Initial react editor website template

This commit is contained in:
Rami Bitar
2026-08-29 14:03:50 -04:00
commit 3e072967e6
61 changed files with 7041 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
'use client';
import { useState, type FormEvent } from 'react';
import { ArrowRight, Mail, MapPin } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
export type ContactFormProps = {
heading?: string;
description?: string;
email?: string;
location?: string;
nameLabel?: string;
emailLabel?: string;
companyLabel?: string;
messageLabel?: string;
submitLabel?: string;
successMessage?: string;
};
export default function ContactForm({
heading = '', description = '', email = '', location = '', nameLabel = 'Name', emailLabel = 'Email', companyLabel = 'Company', messageLabel = 'Message', submitLabel = 'Send message', successMessage = 'Thanks — we will be in touch soon.',
}: ContactFormProps) {
const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
const [error, setError] = useState('');
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setStatus('submitting');
setError('');
const form = event.currentTarget;
const body = Object.fromEntries(new FormData(form).entries());
try {
const response = await fetch('/api/contact', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
});
const result = await response.json() as { error?: string };
if (!response.ok) throw new Error(result.error || 'We could not send your message.');
form.reset();
setStatus('success');
} catch (cause) {
setError(cause instanceof Error ? cause.message : 'We could not send your message.');
setStatus('error');
}
}
return (
<main className="bg-background py-16 sm:py-24">
<div className="section-shell grid gap-12 lg:grid-cols-[.8fr_1.2fr] lg:gap-20">
<div>
<h1 className="text-5xl font-normal leading-[0.96] tracking-[-0.05em] sm:text-6xl lg:text-7xl">{heading}</h1>
{description && <p className="mt-6 max-w-md text-lg leading-8 text-muted-foreground">{description}</p>}
<div className="mt-10 space-y-4 text-sm">
{email && <a href={`mailto:${email}`} className="flex items-center gap-3 hover:text-brand"><Mail className="size-4 text-muted-foreground" aria-hidden="true" />{email}</a>}
{location && <p className="flex items-center gap-3"><MapPin className="size-4 text-muted-foreground" aria-hidden="true" />{location}</p>}
</div>
</div>
<form onSubmit={handleSubmit} className="border-t pt-8">
<div className="grid gap-5 sm:grid-cols-2">
<Field label={nameLabel} htmlFor="contact-name"><Input id="contact-name" name="name" autoComplete="name" required /></Field>
<Field label={emailLabel} htmlFor="contact-email"><Input id="contact-email" name="email" type="email" autoComplete="email" required /></Field>
<Field label={companyLabel} htmlFor="contact-company" className="sm:col-span-2"><Input id="contact-company" name="company" autoComplete="organization" /></Field>
<Field label={messageLabel} htmlFor="contact-message" className="sm:col-span-2"><Textarea id="contact-message" name="message" rows={6} required /></Field>
</div>
<div className="sr-only" aria-hidden="true"><label htmlFor="contact-website">Website</label><input id="contact-website" name="website" tabIndex={-1} autoComplete="off" /></div>
<div className="mt-6 flex flex-col items-start gap-4 sm:flex-row sm:items-center">
<Button type="submit" size="lg" disabled={status === 'submitting'}>{status === 'submitting' ? 'Sending…' : submitLabel}<ArrowRight aria-hidden="true" /></Button>
<p aria-live="polite" className={`text-sm ${status === 'error' ? 'text-destructive' : 'text-muted-foreground'}`}>{status === 'success' ? successMessage : status === 'error' ? error : ''}</p>
</div>
</form>
</div>
</main>
);
}
function Field({ label, htmlFor, className = '', children }: { label: string; htmlFor: string; className?: string; children: React.ReactNode }) {
return <div className={className}><label htmlFor={htmlFor} className="mb-2 block text-sm font-medium">{label}</label>{children}</div>;
}