remove submit ticket

This commit is contained in:
null 2026-05-26 12:55:23 -05:00
parent 85d7ae4bb1
commit 335601b00e
1 changed files with 67 additions and 275 deletions

View File

@ -1,13 +1,4 @@
import SEO from '@/components/SEO'
import { useState } from 'react'
import { toast } from 'sonner'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
import { Select } from '@/components/ui/Select'
import RecaptchaPlaceholder from '@/components/RecaptchaPlaceholder'
import { submitSupport } from '@/lib/api'
import { useDebounce } from '@/hooks/useDebounce'
import { AlertCircle, ArrowRight, CheckCircle2, Clock3, ExternalLink, Headphones, LifeBuoy, ShieldCheck, TicketCheck, Wrench } from 'lucide-react'
const portalLinks = [
@ -37,114 +28,6 @@ const supportedSystems = [
]
const Support = () => {
const [formState, setFormState] = useState({
name: '',
company: '',
email: '',
phone: '',
issue: '',
priority: 'medium',
recaptcha_token: '',
company_website: '', // Honeypot field - hidden from humans, bots will fill it
})
const [errors, setErrors] = useState({
name: '',
company: '',
email: '',
issue: '',
recaptcha_token: '',
})
// Debounce validation errors so they don't flash on every keystroke
const debouncedErrors = useDebounce(errors, 300)
const [isSubmitting, setIsSubmitting] = useState(false)
const validateForm = () => {
const newErrors = {
name: '',
company: '',
email: '',
issue: '',
recaptcha_token: '',
}
// Validate required fields
if (!formState.name.trim()) newErrors.name = 'Name is required'
if (!formState.company.trim()) newErrors.company = 'Company name is required'
if (!formState.issue.trim()) newErrors.issue = 'Please describe your issue'
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!formState.email.trim()) {
newErrors.email = 'Email is required'
} else if (!emailRegex.test(formState.email)) {
newErrors.email = 'Please enter a valid email address'
}
// Validate issue minimum length (10 chars matches server-side Zod rule)
if (formState.issue.trim() && formState.issue.trim().length < 10) {
newErrors.issue = 'Issue description must be at least 10 characters'
}
const hasErrors = Object.values(newErrors).some(error => error !== '')
setErrors(newErrors)
if (hasErrors) {
toast.error('Please fix the errors in the form')
return false
}
return true
}
const handleSubmit = (e) => {
e.preventDefault()
if (!validateForm()) return
handleSubmitForm()
}
const handleSubmitForm = async () => {
setIsSubmitting(true)
try {
await submitSupport(formState)
toast.success("Thanks! We\'ll get back to you soon.")
setFormState({
name: '',
company: '',
email: '',
phone: '',
issue: '',
priority: 'medium',
recaptcha_token: '',
company_website: '',
})
setErrors({
name: '',
company: '',
email: '',
issue: '',
recaptcha_token: '',
})
} catch (error) {
if (error.response?.status === 400 && error.fields) {
setErrors(prev => ({ ...prev, ...error.fields }))
toast.error('Please fix the errors in the form')
} else {
toast.error(error.message || 'Failed to submit form. Please try again.')
}
} finally {
setIsSubmitting(false)
}
}
const handleChange = (e) => {
const { name, value } = e.target
setFormState(prev => ({ ...prev, [name]: value }))
// Clear error for this field as user types
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }))
}
}
return (
<>
<SEO
@ -174,20 +57,25 @@ const Support = () => {
Get help without getting handed off.
</h1>
<p className="mt-6 text-lg md:text-xl text-white/75 max-w-2xl leading-relaxed">
Open a support request, access the client portal, or escalate a service-impacting issue through one clear path.
Sign in to the client portal to manage tickets, create a new support request, or escalate a service-impacting issue through one clear path.
</p>
<div className="mt-8 flex flex-col sm:flex-row gap-3">
<a href="#support-request" className="inline-flex h-11 items-center justify-center gap-2 rounded-md bg-white px-5 text-sm font-semibold text-primary-navy hover:bg-section-alt transition-colors">
Submit Request
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</a>
<a
href={portalLinks[0].href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex h-11 items-center justify-center gap-2 rounded-md bg-white px-5 text-sm font-semibold text-primary-navy hover:bg-section-alt transition-colors"
>
Sign In
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</a>
<a
href={portalLinks[1].href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex h-11 items-center justify-center gap-2 rounded-md border border-white/40 px-5 text-sm font-semibold text-white hover:bg-white/10 transition-colors"
>
Open Portal
Create Account
<ExternalLink className="h-4 w-4" aria-hidden="true" />
</a>
</div>
@ -202,7 +90,7 @@ const Support = () => {
</span>
<div>
<h3 className="font-semibold">Existing client</h3>
<p className="text-sm text-white/70">Submit the form or use the portal to track requests.</p>
<p className="text-sm text-white/70">Sign in to create, update, and track support tickets.</p>
</div>
</div>
<div className="flex gap-4">
@ -222,7 +110,7 @@ const Support = () => {
</span>
<div>
<h3 className="font-semibold">Planned work</h3>
<p className="text-sm text-white/70">Use the request form for moves, adds, changes, and deployments.</p>
<p className="text-sm text-white/70">Use the portal for moves, adds, changes, and deployments.</p>
</div>
</div>
</div>
@ -231,12 +119,11 @@ const Support = () => {
</div>
</section>
{/* Support Form */}
<section id="support-request" className="bg-background py-16 lg:py-24">
{/* Support Portal */}
<section id="support-portal" className="bg-background py-16 lg:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-[0.9fr_1.1fr] gap-8 lg:gap-10 items-start">
{/* Left - Info — order 2 on mobile so form appears first */}
<div className="order-2 lg:order-1 space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-[0.95fr_1.05fr] gap-8 lg:gap-10 items-start">
<div className="space-y-6">
<div className="rounded-md border border-border border-t-[3px] border-t-primary-blue bg-white p-6 shadow-sm">
<div className="flex items-start gap-4">
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-md bg-primary-navy text-white">
@ -245,7 +132,7 @@ const Support = () => {
<div>
<h2 className="text-xl font-bold text-primary-navy">Support Center</h2>
<p className="mt-2 text-sm leading-relaxed text-soft-text">
Sign in to manage existing tickets, create an account, or submit the form on this page for a new request.
Use the client portal to create requests, manage existing tickets, add updates, and keep support history in one place.
</p>
</div>
</div>
@ -304,160 +191,65 @@ const Support = () => {
</div>
</div>
{/* Right - Form — order 1 on mobile so it appears first */}
<div className="order-1 lg:order-2">
<form onSubmit={handleSubmit} noValidate className={`rounded-md border border-border border-t-[3px] border-t-accent-gold bg-white p-6 shadow-sm lg:p-8 space-y-5 ${isSubmitting ? 'opacity-70 pointer-events-none' : ''}`}>
<div className="border-b border-border pb-5">
<h2 className="text-2xl font-bold text-primary-navy">Submit a Request</h2>
<p className="mt-2 text-sm text-soft-text">
Include who is affected, what changed, and how urgent the issue is.
<div>
<div className="rounded-md border border-border border-t-[3px] border-t-accent-gold bg-white p-6 shadow-sm lg:p-8">
<div className="flex items-start gap-4 border-b border-border pb-6">
<span className="flex h-12 w-12 shrink-0 items-center justify-center rounded-md bg-primary-navy text-white">
<TicketCheck className="h-6 w-6" aria-hidden="true" />
</span>
<div>
<p className="text-sm font-semibold uppercase tracking-wide text-primary-blue">Client Portal</p>
<h2 className="mt-2 text-3xl font-bold text-primary-navy">Manage support in Zoho Desk.</h2>
<p className="mt-3 text-sm leading-relaxed text-soft-text">
Existing clients can sign in to submit new tickets, review open items, and keep communications tied to the right account.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-text mb-2">
Name <span className="text-red-600">*</span>
</label>
<Input
type="text"
id="name"
name="name"
value={formState.name}
onChange={handleChange}
required
placeholder="Your full name"
className={errors.name ? 'border-red-500 focus-visible:ring-red-500' : ''}
/>
{debouncedErrors.name && (
<p className="text-xs text-red-600 mt-1">{debouncedErrors.name}</p>
)}
</div>
<div>
<label htmlFor="company" className="block text-sm font-medium text-text mb-2">
Company <span className="text-red-600">*</span>
</label>
<Input
type="text"
id="company"
name="company"
value={formState.company}
onChange={handleChange}
required
placeholder="Company name"
className={errors.company ? 'border-red-500 focus-visible:ring-red-500' : ''}
/>
{debouncedErrors.company && (
<p className="text-xs text-red-600 mt-1">{debouncedErrors.company}</p>
)}
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-text mb-2">
Email <span className="text-red-600">*</span>
</label>
<Input
type="email"
id="email"
name="email"
value={formState.email}
onChange={handleChange}
required
placeholder="you@example.com"
className={errors.email ? 'border-red-500 focus-visible:ring-red-500' : ''}
/>
{debouncedErrors.email && (
<p className="text-xs text-red-600 mt-1">{debouncedErrors.email}</p>
)}
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-text mb-2">
Phone <span className="text-soft-text">(optional)</span>
</label>
<Input
type="tel"
id="phone"
name="phone"
value={formState.phone}
onChange={handleChange}
placeholder="(555) 123-4567"
/>
</div>
</div>
<div>
<label htmlFor="priority" className="block text-sm font-medium text-text mb-2">
Priority <span className="text-red-600">*</span>
</label>
<Select
id="priority"
name="priority"
value={formState.priority}
onChange={handleChange}
<div className="mt-6 grid gap-3 sm:grid-cols-2">
<a
href={portalLinks[0].href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex h-11 items-center justify-center gap-2 rounded-md bg-primary-navy px-5 text-sm font-semibold text-white hover:bg-primary-navy-dark transition-colors"
>
<option value="low">Low - General inquiries (24 hours)</option>
<option value="medium">Medium - Standard issues (4 hours)</option>
<option value="high">High - Critical issues (1 hour)</option>
</Select>
</div>
<div>
<label htmlFor="issue" className="block text-sm font-medium text-text mb-2">
Issue Details <span className="text-red-600">*</span>
</label>
<Textarea
id="issue"
name="issue"
value={formState.issue}
onChange={handleChange}
required
placeholder="What changed, who is affected, and what have you already tried?"
className={`w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-[#F8FAFC] placeholder:text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-navy focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${errors.issue ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
rows={5}
/>
{debouncedErrors.issue && (
<p className="text-xs text-red-600 mt-1">{debouncedErrors.issue}</p>
)}
</div>
<div className="absolute opacity-0 h-0 overflow-hidden" aria-hidden="true">
<input
type="text"
name="company_website"
tabIndex="-1"
autoComplete="off"
value={formState.company_website}
onChange={handleChange}
/>
</div>
<RecaptchaPlaceholder error={debouncedErrors.recaptcha_token} />
<Button
type="submit"
className="w-full gap-2"
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Submitting...
</>
) : (
<>
Submit Request
Sign In
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</>
)}
</Button>
</form>
</a>
<a
href={portalLinks[1].href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex h-11 items-center justify-center gap-2 rounded-md border border-border bg-white px-5 text-sm font-semibold text-primary-navy hover:bg-section-alt transition-colors"
>
Create Account
<ExternalLink className="h-4 w-4" aria-hidden="true" />
</a>
</div>
<div className="mt-8 grid gap-3">
{[
'Submit and track support tickets',
'Add updates without starting a new thread',
'Keep request history tied to your account',
].map((item) => (
<div key={item} className="flex items-center gap-3 rounded-md border border-border bg-background p-4 text-sm font-medium text-text">
<CheckCircle2 className="h-5 w-5 shrink-0 text-primary-blue" aria-hidden="true" />
{item}
</div>
))}
</div>
<div className="mt-8 rounded-md border border-border bg-section-alt p-4">
<p className="text-sm font-semibold text-primary-navy">Service-impacting issue?</p>
<p className="mt-1 text-sm text-soft-text">
Call Queue North directly for urgent outages or time-sensitive escalations.
</p>
<a href="tel:+13217308020" className="mt-3 inline-flex text-sm font-semibold text-primary-blue hover:text-primary-navy transition-colors">
(321) 730-8020
</a>
</div>
</div>
</div>
</div>
</div>