Add complete project files

This commit is contained in:
2025-08-26 08:59:13 +02:00
parent 2756116b75
commit 1da752da34
69 changed files with 11933 additions and 0 deletions

96
components/ServiceSection.tsx Executable file
View File

@@ -0,0 +1,96 @@
import React from "react";
import Image from "next/image";
type Service = {
slug: string;
title: string;
subtitle: string;
blurb: string;
whatWeDo: string[];
outcomes: string[];
iconName?: string;
image?: string;
};
type Props = {
service: Service;
index: number;
};
export default function ServiceSection({ service, index }: Props) {
const isReversed = index % 2 === 1;
return (
<section id={service.slug} className="py-16 sm:py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div
className={`grid grid-cols-1 lg:grid-cols-2 gap-10 lg:gap-16 ${
isReversed ? "lg:[&>*:first-child]:order-2" : ""
}`}
>
{/* Text column */}
<div>
<div className="flex items-center gap-3 mb-4">
{service.image && (
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-navy mr-4">
<Image
src={service.image}
alt={service.title}
width={64}
height={64}
className="h-12 w-12 object-cover rounded"
/>
</div>
)}
<div className="text-sm">{service.subtitle}</div>
</div>
<h2 className="text-2xl font-semibold">{service.title}</h2>
<p className="mt-3">{service.blurb}</p>
<div className="mt-8 grid grid-cols-1 sm:grid-cols-2 gap-8">
<div>
<h3 className="font-medium mb-2">What We Do</h3>
<ul className="list-disc pl-5 space-y-1">
{service.whatWeDo.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
<div>
<h3 className="font-medium mb-2">Outcomes</h3>
<ul className="list-disc pl-5 space-y-1">
{service.outcomes.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
</div>
<div className="mt-8">
<a href={`/services/${service.slug}`} className="inline-block px-4 py-2 border rounded-md">
Learn More About {service.title}
</a>
</div>
</div>
{/* Visual card */}
<div className="border rounded-xl p-8 flex flex-col items-center justify-center text-center">
{service.image && (
<div className="mb-4 h-24 w-24 border rounded-lg overflow-hidden">
<Image
src={service.image}
alt={service.title}
width={96}
height={96}
className="h-full w-full object-cover"
/>
</div>
)}
<div className="text-lg font-medium">{service.title}</div>
<div className="text-sm text-gray-600">{service.subtitle}</div>
</div>
</div>
</div>
</section>
);
}

46
components/breadcrumbs.tsx Executable file
View File

@@ -0,0 +1,46 @@
import Link from 'next/link';
import { ChevronRight, Home } from 'lucide-react';
interface BreadcrumbItem {
label: string;
href?: string;
}
interface BreadcrumbsProps {
items: BreadcrumbItem[];
}
export function Breadcrumbs({ items }: BreadcrumbsProps) {
return (
<nav className="flex" aria-label="Breadcrumb">
<ol className="flex items-center space-x-2">
<li>
<Link
href="/"
className="text-slate-500 hover:text-navy transition-colors"
>
<Home className="h-4 w-4" />
<span className="sr-only">Home</span>
</Link>
</li>
{items.map((item, index) => (
<li key={index} className="flex items-center">
<ChevronRight className="h-4 w-4 text-slate-400" />
{item.href ? (
<Link
href={item.href}
className="ml-2 text-sm text-slate-500 hover:text-navy transition-colors"
>
{item.label}
</Link>
) : (
<span className="ml-2 text-sm font-medium text-ink">
{item.label}
</span>
)}
</li>
))}
</ol>
</nav>
);
}

187
components/contact-form.tsx Executable file
View File

@@ -0,0 +1,187 @@
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { CheckCircle, AlertCircle } from 'lucide-react';
interface ContactFormData {
name: string;
email: string;
phone?: string;
message: string;
}
export function ContactForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle');
const [formData, setFormData] = useState<ContactFormData>({
name: '',
email: '',
phone: '',
message: '',
});
const [errors, setErrors] = useState<Partial<ContactFormData>>({});
const validateForm = (): boolean => {
const newErrors: Partial<ContactFormData> = {};
if (!formData.name.trim()) {
newErrors.name = 'Name is required';
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = 'Email is invalid';
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) {
return;
}
setIsSubmitting(true);
setSubmitStatus('idle');
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (response.ok) {
setSubmitStatus('success');
setFormData({ name: '', email: '', phone: '', message: '' });
} else {
setSubmitStatus('error');
}
} catch (error) {
setSubmitStatus('error');
} finally {
setIsSubmitting(false);
}
};
const handleInputChange = (field: keyof ContactFormData, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }));
}
};
return (
<div className="max-w-2xl mx-auto">
{submitStatus === 'success' && (
<div className="mb-6 p-4 bg-green-50 border border-green-200 rounded-md">
<div className="flex items-center">
<CheckCircle className="h-5 w-5 text-green-600 mr-2" />
<p className="text-green-800">
Thank you for your message! We'll get back to you within 24 hours.
</p>
</div>
</div>
)}
{submitStatus === 'error' && (
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-md">
<div className="flex items-center">
<AlertCircle className="h-5 w-5 text-red-600 mr-2" />
<p className="text-red-800">
There was an error sending your message. Please try again or call us directly.
</p>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-ink mb-2">
Full Name *
</label>
<Input
id="name"
type="text"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
className={errors.name ? 'border-red-500' : ''}
placeholder="Your full name"
/>
{errors.name && (
<p className="mt-1 text-sm text-red-600">{errors.name}</p>
)}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-ink mb-2">
Email Address *
</label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
className={errors.email ? 'border-red-500' : ''}
placeholder="your.email@example.com"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email}</p>
)}
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-ink mb-2">
Phone Number (Optional)
</label>
<Input
id="phone"
type="tel"
value={formData.phone}
onChange={(e) => handleInputChange('phone', e.target.value)}
placeholder="(555) 123-4567"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-ink mb-2">
Message *
</label>
<Textarea
id="message"
value={formData.message}
onChange={(e) => handleInputChange('message', e.target.value)}
className={errors.message ? 'border-red-500' : ''}
placeholder="Tell us about your tax needs..."
rows={5}
/>
{errors.message && (
<p className="mt-1 text-sm text-red-600">{errors.message}</p>
)}
</div>
<Button
type="submit"
disabled={isSubmitting}
className="w-full"
size="lg"
>
{isSubmitting ? 'Sending...' : 'Send Message'}
</Button>
</form>
</div>
);
}

71
components/cta-section.tsx Executable file
View File

@@ -0,0 +1,71 @@
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Phone, Mail } from 'lucide-react';
interface CTASectionProps {
title: string;
description: string;
primaryCta: {
text: string;
href: string;
icon?: 'phone' | 'mail';
};
secondaryCta?: {
text: string;
href: string;
};
variant?: 'default' | 'dark';
}
export function CTASection({
title,
description,
primaryCta,
secondaryCta,
variant = 'default',
}: CTASectionProps) {
const isDark = variant === 'dark';
const bgClass = isDark ? 'bg-navy' : 'bg-teal';
const textClass = isDark ? 'text-cloud' : 'text-cloud';
const getIcon = () => {
if (primaryCta.icon === 'phone') return <Phone className="mr-2 h-4 w-4" />;
if (primaryCta.icon === 'mail') return <Mail className="mr-2 h-4 w-4" />;
return null;
};
return (
<div className={`${bgClass} py-16 sm:py-20`}>
<div className="mx-auto max-w-7xl px-4 lg:px-6">
<div className="mx-auto max-w-2xl text-center">
<h2 className={`text-3xl font-bold tracking-tight ${textClass} sm:text-4xl`}>
{title}
</h2>
<p className={`mt-6 text-lg leading-8 ${isDark ? 'text-slate-300' : 'text-slate-100'}`}>
{description}
</p>
<div className="mt-8 flex items-center justify-center gap-x-6">
<Button
asChild
size="lg"
variant={isDark ? 'secondary' : 'default'}
>
<Link href={primaryCta.href}>
{getIcon()}
{primaryCta.text}
</Link>
</Button>
{secondaryCta && (
<Link
href={secondaryCta.href}
className={`text-sm font-semibold leading-6 ${textClass} hover:opacity-80 transition-opacity`}
>
{secondaryCta.text} <span aria-hidden="true"></span>
</Link>
)}
</div>
</div>
</div>
</div>
);
}

66
components/hero.tsx Executable file
View File

@@ -0,0 +1,66 @@
import Link from 'next/link';
import Image from 'next/image';
import { Button } from '@/components/ui/button';
interface HeroProps {
title: string;
subtitle: string;
ctaText: string;
ctaHref: string;
imageSrc: string;
imageAlt: string;
reverse?: boolean;
}
export function Hero({
title,
subtitle,
ctaText,
ctaHref,
imageSrc,
imageAlt,
reverse = false,
}: HeroProps) {
return (
<div className="relative isolate overflow-hidden bg-navy">
<div className="mx-auto max-w-7xl px-4 pb-16 pt-8 sm:pb-24 lg:flex lg:px-6 lg:py-24">
<div className={`mx-auto max-w-2xl flex-shrink-0 lg:mx-0 lg:max-w-xl lg:pt-8 ${reverse ? 'lg:order-2' : ''}`}>
<div className="mt-16 sm:mt-20 lg:mt-12">
<Link href="/contact" className="inline-flex space-x-6">
<span className="rounded-full bg-teal/10 px-3 py-1 text-sm font-semibold leading-6 text-teal ring-1 ring-inset ring-teal/10">
Book Your Consultation
</span>
</Link>
</div>
<h1 className="mt-10 text-4xl font-bold tracking-tight text-cloud sm:text-6xl">
{title}
</h1>
<p className="mt-6 text-lg leading-8 text-cloud/90">
{subtitle}
</p>
<div className="mt-10 flex items-center gap-x-6">
<Button asChild size="lg">
<Link href={ctaHref}>
{ctaText}
</Link>
</Button>
<Link href="/about" className="text-sm font-semibold leading-6 text-cloud hover:text-cloud/80 transition-colors">
Learn more <span aria-hidden="true"></span>
</Link>
</div>
</div>
<div className={`mx-auto mt-12 flex max-w-2xl sm:mt-16 lg:ml-8 lg:mr-0 lg:mt-0 lg:max-w-none lg:flex-none xl:ml-20 ${reverse ? 'lg:order-1' : ''}`}>
<div className="max-w-2xl flex-none sm:max-w-3xl lg:max-w-none">
<Image
src={imageSrc}
alt={imageAlt}
width={1200}
height={800}
className="w-[40rem] rounded-md bg-white/5 shadow-2xl ring-1 ring-white/10"
/>
</div>
</div>
</div>
</div>
);
}

56
components/process-steps.tsx Executable file
View File

@@ -0,0 +1,56 @@
import { Calendar, Upload, CheckCircle } from 'lucide-react';
const steps = [
{
id: 1,
name: 'Book',
description: 'Schedule your consultation with our tax experts',
icon: Calendar,
},
{
id: 2,
name: 'Prepare',
description: 'Securely upload your documents and we prepare your returns',
icon: Upload,
},
{
id: 3,
name: 'Delivered',
description: 'Receive your completed returns with detailed explanations',
icon: CheckCircle,
},
];
export function ProcessSteps() {
return (
<div className="bg-cloud py-16 sm:py-20">
<div className="mx-auto max-w-7xl px-4 lg:px-6">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-3xl font-bold tracking-tight text-ink sm:text-4xl">
Simple 3-Step Process
</h2>
<p className="mt-6 text-lg leading-8 text-slate">
We make tax preparation straightforward and stress-free. Here's how we work together to get your taxes done right.
</p>
</div>
<div className="mx-auto mt-12 max-w-2xl sm:mt-16 lg:mt-20 lg:max-w-none">
<dl className="grid max-w-xl grid-cols-1 gap-x-8 gap-y-16 lg:max-w-none lg:grid-cols-3">
{steps.map((step) => (
<div key={step.id} className="flex flex-col">
<dt className="flex items-center gap-x-3 text-base font-semibold leading-7 text-ink">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-navy">
<step.icon className="h-6 w-6 text-cloud" aria-hidden="true" />
</div>
Step {step.id}: {step.name}
</dt>
<dd className="mt-4 flex flex-auto flex-col text-base leading-7 text-slate">
<p className="flex-auto">{step.description}</p>
</dd>
</div>
))}
</dl>
</div>
</div>
</div>
);
}

115
components/secure-upload.tsx Executable file
View File

@@ -0,0 +1,115 @@
'use client';
import { useState } from 'react';
import { Upload, File, X, CheckCircle } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
export function SecureUpload() {
const [files, setFiles] = useState<File[]>([]);
const [isUploading, setIsUploading] = useState(false);
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedFiles = Array.from(event.target.files || []);
setFiles(prev => [...prev, ...selectedFiles]);
};
const removeFile = (index: number) => {
setFiles(prev => prev.filter((_, i) => i !== index));
};
const handleUpload = async () => {
setIsUploading(true);
// TODO: Implement actual file upload to S3 or similar service
// This is a stub implementation
await new Promise(resolve => setTimeout(resolve, 2000));
setIsUploading(false);
setFiles([]);
};
return (
<Card className="max-w-2xl mx-auto">
<CardHeader>
<CardTitle>Secure Document Upload</CardTitle>
<CardDescription>
Upload your tax documents securely. All files are encrypted and stored safely.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="border-2 border-dashed border-slate-300 rounded-lg p-6 text-center">
<Upload className="mx-auto h-12 w-12 text-slate-400" />
<div className="mt-4">
<label htmlFor="file-upload" className="cursor-pointer">
<span className="text-sm font-medium text-navy hover:text-teal">
Click to upload
</span>
<span className="text-slate-500"> or drag and drop</span>
</label>
<input
id="file-upload"
name="file-upload"
type="file"
multiple
className="sr-only"
onChange={handleFileSelect}
accept=".pdf,.jpg,.jpeg,.png,.doc,.docx"
/>
</div>
<p className="text-xs text-slate-500 mt-2">
PDF, JPG, PNG, DOC up to 10MB each
</p>
</div>
{files.length > 0 && (
<div className="space-y-3">
<h4 className="font-medium text-ink">Selected Files:</h4>
{files.map((file, index) => (
<div key={index} className="flex items-center justify-between p-3 bg-sand rounded-lg">
<div className="flex items-center space-x-3">
<File className="h-5 w-5 text-navy" />
<div>
<p className="text-sm font-medium text-ink">{file.name}</p>
<p className="text-xs text-slate">
{(file.size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
</div>
<button
onClick={() => removeFile(index)}
className="text-slate-400 hover:text-red-500"
>
<X className="h-4 w-4" />
</button>
</div>
))}
</div>
)}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex items-start space-x-3">
<CheckCircle className="h-5 w-5 text-blue-600 mt-0.5" />
<div className="text-sm text-blue-800">
<p className="font-medium">Secure & Private</p>
<p className="mt-1">
Your documents are encrypted and stored securely. Only authorized personnel can access your files.
</p>
</div>
</div>
</div>
<Button
onClick={handleUpload}
disabled={files.length === 0 || isUploading}
className="w-full"
size="lg"
>
{isUploading ? 'Uploading...' : 'Upload Documents'}
</Button>
<p className="text-xs text-slate-500 text-center">
Note: This is a demo interface. In production, this would integrate with a secure file storage service.
</p>
</CardContent>
</Card>
);
}

51
components/service-card.tsx Executable file
View File

@@ -0,0 +1,51 @@
import Link from 'next/link';
import Image from 'next/image';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { ArrowRight } from 'lucide-react';
interface ServiceCardProps {
title: string;
description: string;
image: string;
imageAlt: string;
href: string;
features?: string[];
}
export function ServiceCard({ title, description, image, imageAlt, href, features }: ServiceCardProps) {
return (
<Card className="overflow-hidden hover:shadow-lg transition-shadow">
<div className="aspect-square relative">
<Image
src={image}
alt={imageAlt}
fill
className="object-cover"
/>
</div>
<CardHeader>
<CardTitle className="text-xl">{title}</CardTitle>
<CardDescription className="text-base">{description}</CardDescription>
</CardHeader>
<CardContent>
{features && (
<ul className="space-y-2 mb-6">
{features.slice(0, 3).map((feature, index) => (
<li key={index} className="flex items-center text-sm text-slate">
<div className="w-1.5 h-1.5 bg-teal rounded-full mr-2" />
{feature}
</li>
))}
</ul>
)}
<Button asChild className="w-full">
<Link href={href}>
Learn More
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</CardContent>
</Card>
);
}

123
components/site-footer.tsx Executable file
View File

@@ -0,0 +1,123 @@
import Link from 'next/link';
import { Mail, Phone, MapPin, Clock } from 'lucide-react';
const navigation = {
services: [
{ name: 'Public Accounting', href: '/services/public-accounting' },
{ name: 'Federal Income Tax', href: '/services/federal-income-tax' },
{ name: 'Estate Tax Planning', href: '/services/estate-tax-planning' },
{ name: 'Financial Planning', href: '/services/financial-planning' },
{ name: 'Advisory Services', href: '/services/advisory' },
],
company: [
{ name: 'About', href: '/about' },
{ name: 'Pricing', href: '/pricing' },
{ name: 'Reviews', href: '/reviews' },
{ name: 'Contact', href: '/contact' },
],
legal: [
{ name: 'Privacy Policy', href: '/legal/privacy' },
{ name: 'Terms of Service', href: '/legal/terms' },
],
};
export function SiteFooter() {
return (
<footer className="bg-navy text-cloud" aria-labelledby="footer-heading">
<h2 id="footer-heading" className="sr-only">
Footer
</h2>
<div className="mx-auto max-w-7xl px-4 pb-6 pt-12 sm:pt-16 lg:px-6 lg:pt-20">
<div className="xl:grid xl:grid-cols-3 xl:gap-8">
<div className="space-y-8">
<div className="text-2xl font-bold">Hampton, Brown & Associates, PC</div>
<p className="text-sm leading-6 text-slate-300">
Expert federal income and estate tax services, financial planning, and goal-oriented strategies in Corpus Christi, Texas.
We assist our clients with their tax and accounting needs and achieving economic goals.
</p>
<div className="space-y-4">
<div className="flex items-center space-x-3">
<MapPin className="h-5 w-5 text-teal" />
<span className="text-sm">Corpus Christi, TX</span>
</div>
<div className="flex items-center space-x-3">
<Phone className="h-5 w-5 text-teal" />
<span className="text-sm">(361) 888-7711</span>
</div>
<div className="flex items-center space-x-3">
<Mail className="h-5 w-5 text-teal" />
<span className="text-sm">info@hamptonbrown.com</span>
</div>
<div className="flex items-center space-x-3">
<Clock className="h-5 w-5 text-teal" />
<span className="text-sm">Mon-Fri 9AM-5PM</span>
</div>
</div>
</div>
<div className="mt-16 grid grid-cols-2 gap-8 xl:col-span-2 xl:mt-0">
<div className="md:grid md:grid-cols-2 md:gap-8">
<div>
<h3 className="text-sm font-semibold leading-6">Services</h3>
<ul role="list" className="mt-6 space-y-4">
{navigation.services.map((item) => (
<li key={item.name}>
<Link href={item.href} className="text-sm leading-6 text-slate-300 hover:text-cloud transition-colors">
{item.name}
</Link>
</li>
))}
</ul>
</div>
<div className="mt-10 md:mt-0">
<h3 className="text-sm font-semibold leading-6">Company</h3>
<ul role="list" className="mt-6 space-y-4">
{navigation.company.map((item) => (
<li key={item.name}>
<Link href={item.href} className="text-sm leading-6 text-slate-300 hover:text-cloud transition-colors">
{item.name}
</Link>
</li>
))}
</ul>
</div>
</div>
<div className="md:grid md:grid-cols-2 md:gap-8">
<div>
<h3 className="text-sm font-semibold leading-6">Legal</h3>
<ul role="list" className="mt-6 space-y-4">
{navigation.legal.map((item) => (
<li key={item.name}>
<Link href={item.href} className="text-sm leading-6 text-slate-300 hover:text-cloud transition-colors">
{item.name}
</Link>
</li>
))}
</ul>
</div>
<div className="mt-10 md:mt-0">
<h3 className="text-sm font-semibold leading-6">Resources</h3>
<ul role="list" className="mt-6 space-y-4">
<li>
<Link href="/resources" className="text-sm leading-6 text-slate-300 hover:text-cloud transition-colors">
Secure Upload
</Link>
</li>
<li>
<Link href="/resources" className="text-sm leading-6 text-slate-300 hover:text-cloud transition-colors">
Payment Portal
</Link>
</li>
</ul>
</div>
</div>
</div>
</div>
<div className="mt-12 border-t border-slate-700 pt-6 sm:mt-16 lg:mt-20">
<p className="text-xs leading-5 text-slate-400">
&copy; {new Date().getFullYear()} Hampton, Brown & Associates, PC. All rights reserved.
</p>
</div>
</div>
</footer>
);
}

107
components/site-header.tsx Executable file
View File

@@ -0,0 +1,107 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
import { Menu, X, Phone } from 'lucide-react';
import { Button } from '@/components/ui/button';
const navigation = [
{ name: 'Services', href: '/services' },
{ name: 'About', href: '/about' },
{ name: 'Resources', href: '/resources' },
{ name: 'Reviews', href: '/reviews' },
{ name: 'Contact', href: '/contact' },
];
export function SiteHeader() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<header className="bg-cloud shadow-sm">
<nav className="mx-auto flex max-w-7xl items-center justify-between p-4 lg:px-6" aria-label="Global">
<div className="flex lg:flex-1">
<Link href="/" className="-m-1.5 p-1.5">
<span className="sr-only">Hampton Brown & Associates, PC</span>
<div className="text-2xl font-bold text-navy">Hampton Brown & Associates, PC</div>
</Link>
</div>
<div className="flex lg:hidden">
<button
type="button"
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-slate"
onClick={() => setMobileMenuOpen(true)}
>
<span className="sr-only">Open main menu</span>
<Menu className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="hidden lg:flex lg:gap-x-12">
{navigation.map((item) => (
<Link
key={item.name}
href={item.href}
className="text-sm font-semibold leading-6 text-ink hover:text-navy transition-colors"
>
{item.name}
</Link>
))}
</div>
<div className="hidden lg:flex lg:flex-1 lg:justify-end">
<Button asChild>
<Link href="/contact">
<Phone className="mr-2 h-4 w-4" />
Book a Call
</Link>
</Button>
</div>
</nav>
{/* Mobile menu */}
{mobileMenuOpen && (
<div className="lg:hidden">
<div className="fixed inset-0 z-50" />
<div className="fixed inset-y-0 right-0 z-50 w-full overflow-y-auto bg-cloud px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-slate-900/10">
<div className="flex items-center justify-between">
<Link href="/" className="-m-1.5 p-1.5">
<span className="sr-only">Hampton Brown & Associates, PC</span>
<div className="text-2xl font-bold text-navy">Hampton Brown & Associates, PC</div>
</Link>
<button
type="button"
className="-m-2.5 rounded-md p-2.5 text-slate"
onClick={() => setMobileMenuOpen(false)}
>
<span className="sr-only">Close menu</span>
<X className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="mt-6 flow-root">
<div className="-my-6 divide-y divide-slate-500/10">
<div className="space-y-2 py-6">
{navigation.map((item) => (
<Link
key={item.name}
href={item.href}
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-ink hover:bg-sand"
onClick={() => setMobileMenuOpen(false)}
>
{item.name}
</Link>
))}
</div>
<div className="py-6">
<Button asChild className="w-full">
<Link href="/contact">
<Phone className="mr-2 h-4 w-4" />
Book a Call
</Link>
</Button>
</div>
</div>
</div>
</div>
</div>
)}
</header>
);
}

44
components/testimonial.tsx Executable file
View File

@@ -0,0 +1,44 @@
import { Star } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
interface TestimonialProps {
quote: string;
author: string;
role: string;
rating: number;
}
export function Testimonial({ quote, author, role, rating }: TestimonialProps) {
return (
<Card className="bg-sand border-0">
<CardContent className="p-6">
<div className="flex mb-4">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`h-5 w-5 ${
i < rating ? 'text-yellow-400 fill-current' : 'text-slate-300'
}`}
/>
))}
</div>
<blockquote className="text-lg font-medium text-ink mb-4">
"{quote}"
</blockquote>
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-10 h-10 bg-navy rounded-full flex items-center justify-center">
<span className="text-cloud font-semibold text-sm">
{author.split(' ').map(n => n[0]).join('')}
</span>
</div>
</div>
<div className="ml-3">
<p className="text-sm font-semibold text-ink">{author}</p>
<p className="text-sm text-slate">{role}</p>
</div>
</div>
</CardContent>
</Card>
);
}

56
components/trust-panel.tsx Executable file
View File

@@ -0,0 +1,56 @@
import { Shield, Lock, CheckCircle } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
const trustFeatures = [
{
icon: Shield,
title: 'Bank-Level Security',
description: 'Your data is protected with enterprise-grade encryption',
},
{
icon: Lock,
title: 'Secure Client Portal',
description: 'Private, password-protected access to your documents',
},
{
icon: CheckCircle,
title: 'IRS Certified',
description: 'Our preparers are IRS certified and stay current with tax law',
},
];
export function TrustPanel() {
return (
<div className="bg-navy py-16 sm:py-20">
<div className="mx-auto max-w-7xl px-4 lg:px-6">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-3xl font-bold tracking-tight text-cloud sm:text-4xl">
Your Security & Trust Are Our Priority
</h2>
<p className="mt-6 text-lg leading-8 text-slate-300">
We understand the sensitive nature of your financial information. That's why we've implemented the highest security standards to protect your data.
</p>
</div>
<div className="mx-auto mt-12 max-w-2xl sm:mt-16 lg:mt-20 lg:max-w-none">
<dl className="grid max-w-xl grid-cols-1 gap-x-8 gap-y-16 lg:max-w-none lg:grid-cols-3">
{trustFeatures.map((feature) => (
<Card key={feature.title} className="bg-slate-800 border-slate-700">
<CardHeader>
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-teal">
<feature.icon className="h-6 w-6 text-cloud" aria-hidden="true" />
</div>
<CardTitle className="text-cloud">{feature.title}</CardTitle>
</CardHeader>
<CardContent>
<CardDescription className="text-slate-300">
{feature.description}
</CardDescription>
</CardContent>
</Card>
))}
</dl>
</div>
</div>
</div>
);
}

36
components/ui/badge.tsx Executable file
View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
'border-transparent bg-navy text-cloud hover:bg-navy/80',
secondary:
'border-transparent bg-teal text-cloud hover:bg-teal/80',
destructive:
'border-transparent bg-red-500 text-cloud hover:bg-red-500/80',
outline: 'text-navy',
},
},
defaultVariants: {
variant: 'default',
},
}
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };

56
components/ui/button.tsx Executable file
View File

@@ -0,0 +1,56 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-navy text-cloud hover:bg-navy/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-teal text-cloud hover:bg-teal/90',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-navy underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };

79
components/ui/card.tsx Executable file
View File

@@ -0,0 +1,79 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-lg border border-slate-200 bg-cloud text-ink shadow-sm',
className
)}
{...props}
/>
));
Card.displayName = 'Card';
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props}
/>
));
CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
'text-2xl font-semibold leading-none tracking-tight text-navy',
className
)}
{...props}
/>
));
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn('text-sm text-slate', className)}
{...props}
/>
));
CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
));
CardContent.displayName = 'CardContent';
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex items-center p-6 pt-0', className)}
{...props}
/>
));
CardFooter.displayName = 'CardFooter';
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

25
components/ui/input.tsx Executable file
View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-slate-200 bg-cloud px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-navy focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
);
}
);
Input.displayName = 'Input';
export { Input };

24
components/ui/textarea.tsx Executable file
View File

@@ -0,0 +1,24 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-slate-200 bg-cloud px-3 py-2 text-sm ring-offset-background placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-navy focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
);
}
);
Textarea.displayName = 'Textarea';
export { Textarea };