MotorSignal-App/website/components/support-contact-form.tsx

154 lines
5.4 KiB
TypeScript

'use client';
import React, { useState } from 'react';
const SUPPORT_EMAIL = 'support@motorsignal.app';
const CATEGORIES = [
'Account & sign-in',
'Subscription & billing',
'Group rides',
'Clubs',
'Telemetry & ride data',
'Bug report',
'Something else',
] as const;
type Errors = { name?: string; email?: string; message?: string };
// Public support contact. There is no public ticket backend, so this never claims a
// ticket was filed: it validates input and opens the visitor's own email client with a
// prefilled message. Email remains the real, working support channel.
export function SupportContactForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [category, setCategory] = useState<(typeof CATEGORIES)[number]>(CATEGORIES[0]);
const [message, setMessage] = useState('');
const [errors, setErrors] = useState<Errors>({});
const [opened, setOpened] = useState(false);
function validate(): Errors {
const next: Errors = {};
if (!name.trim()) next.name = 'Please tell us your name.';
// Pragmatic email check: a single @ with a dot in the domain.
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
next.email = 'Enter a valid email address so we can reply.';
}
if (message.trim().length < 10) {
next.message = 'Add a few more details (at least 10 characters).';
}
return next;
}
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const found = validate();
setErrors(found);
if (Object.keys(found).length > 0) {
setOpened(false);
return;
}
const subject = `[${category}] Support request from ${name.trim()}`;
const body =
`Name: ${name.trim()}\n` +
`Email: ${email.trim()}\n` +
`Category: ${category}\n\n` +
`${message.trim()}\n`;
const mailto = `mailto:${SUPPORT_EMAIL}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
// Open the visitor's email client. We do not and cannot confirm delivery here.
if (typeof window !== 'undefined') {
window.location.href = mailto;
}
setOpened(true);
}
const inputClass =
'w-full px-3.5 py-2.5 rounded-xl bg-surface-container-low border border-outline-variant/40 focus:border-primary-container focus:ring-1 focus:ring-primary-container text-white text-sm outline-none transition placeholder:text-slate-500';
return (
<form className="space-y-4 text-left" onSubmit={handleSubmit} noValidate>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<label htmlFor="support-name" className="block text-xs font-mono tracking-wide text-on-surface-variant">
Your name
</label>
<input
id="support-name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className={inputClass}
placeholder="Alex Rider"
aria-invalid={Boolean(errors.name)}
/>
{errors.name && <p className="form-error" role="alert">{errors.name}</p>}
</div>
<div className="space-y-1.5">
<label htmlFor="support-email" className="block text-xs font-mono tracking-wide text-on-surface-variant">
Email
</label>
<input
id="support-email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className={inputClass}
placeholder="you@example.com"
aria-invalid={Boolean(errors.email)}
/>
{errors.email && <p className="form-error" role="alert">{errors.email}</p>}
</div>
</div>
<div className="space-y-1.5">
<label htmlFor="support-category" className="block text-xs font-mono tracking-wide text-on-surface-variant">
Topic
</label>
<select
id="support-category"
value={category}
onChange={(e) => setCategory(e.target.value as (typeof CATEGORIES)[number])}
className={inputClass}
>
{CATEGORIES.map((c) => (
<option key={c} value={c}>{c}</option>
))}
</select>
</div>
<div className="space-y-1.5">
<label htmlFor="support-message" className="block text-xs font-mono tracking-wide text-on-surface-variant">
How can we help?
</label>
<textarea
id="support-message"
value={message}
onChange={(e) => setMessage(e.target.value)}
rows={5}
className={inputClass}
placeholder="Describe what you ran into, including your device and app version if relevant."
aria-invalid={Boolean(errors.message)}
/>
{errors.message && <p className="form-error" role="alert">{errors.message}</p>}
</div>
<button type="submit" className="button">Compose support email</button>
{opened && (
<p className="form-message" role="status">
Your email app should have opened with the details filled in. If nothing happened, email{' '}
<a className="underline" href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> directly.
</p>
)}
<p className="form-divider">
This opens your own email app — we don&rsquo;t submit a ticket on your behalf, so your message
isn&rsquo;t sent until you send it.
</p>
</form>
);
}