Files
photography/frontend/components/contact-view.tsx
xujiang 3d197eb7e3 feat: setup frontend project with bun and dynamic data fetching
- Create new frontend project directory with Next.js 15 + React 19
- Migrate from pnpm to bun for faster package management
- Add TanStack Query + Axios for dynamic data fetching
- Create comprehensive Makefile with development commands
- Setup API layer with query hooks and error handling
- Configure environment variables and bun settings
- Add TypeScript type checking and project documentation
- Update CLAUDE.md with bun-specific development workflow
2025-07-08 15:28:26 +08:00

433 lines
16 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import type React from "react"
import { useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import {
Mail,
Phone,
MapPin,
Clock,
Send,
CheckCircle,
Instagram,
Twitter,
Linkedin,
Camera,
Users,
Heart,
Star,
MessageCircle,
} from "lucide-react"
export function ContactView() {
const [formData, setFormData] = useState({
name: "",
email: "",
phone: "",
service: "",
message: "",
})
const [isSubmitting, setIsSubmitting] = useState(false)
const [isSubmitted, setIsSubmitted] = useState(false)
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target
setFormData((prev) => ({ ...prev, [name]: value }))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSubmitting(true)
// Simulate form submission
await new Promise((resolve) => setTimeout(resolve, 2000))
setIsSubmitting(false)
setIsSubmitted(true)
// Reset form after 3 seconds
setTimeout(() => {
setIsSubmitted(false)
setFormData({
name: "",
email: "",
phone: "",
service: "",
message: "",
})
}, 3000)
}
const services = [
{
title: "人像摄影",
description: "个人写真、情侣照、家庭照",
price: "¥800-2000/次",
icon: Users,
features: ["专业化妆", "多套服装", "精修10张", "原片全送"],
},
{
title: "婚礼摄影",
description: "婚礼全程跟拍记录",
price: "¥3000-8000/天",
icon: Heart,
features: ["双机位拍摄", "全天跟拍", "精修50张", "婚礼相册"],
},
{
title: "商业摄影",
description: "产品拍摄、企业宣传",
price: "¥1500-5000/次",
icon: Camera,
features: ["产品精修", "多角度拍摄", "商用授权", "快速交付"],
},
{
title: "活动摄影",
description: "会议、庆典、聚会拍摄",
price: "¥1000-3000/次",
icon: Star,
features: ["现场抓拍", "关键时刻", "快速出片", "团队合影"],
},
]
const contactInfo = [
{
icon: Mail,
label: "邮箱",
value: "zhang.minghua@email.com",
link: "mailto:zhang.minghua@email.com",
},
{
icon: Phone,
label: "电话",
value: "+86 138 0000 0000",
link: "tel:+8613800000000",
},
{
icon: MapPin,
label: "地址",
value: "北京市朝阳区798艺术区",
link: "https://maps.google.com",
},
{
icon: Clock,
label: "工作时间",
value: "周一至周日 9:00-21:00",
link: null,
},
]
const socialLinks = [
{ icon: Instagram, label: "Instagram", link: "https://instagram.com" },
{ icon: Twitter, label: "微博", link: "https://weibo.com" },
{ icon: Linkedin, label: "LinkedIn", link: "https://linkedin.com" },
]
const workflowSteps = [
{
step: "01",
title: "初步沟通",
description: "了解您的需求和想法,确定拍摄风格和时间",
},
{
step: "02",
title: "方案制定",
description: "制定详细的拍摄方案,包括地点、服装、道具等",
},
{
step: "03",
title: "正式拍摄",
description: "按照方案进行专业拍摄,确保每个细节完美",
},
{
step: "04",
title: "后期制作",
description: "精心后期处理,呈现最佳视觉效果",
},
{
step: "05",
title: "作品交付",
description: "按时交付高质量的最终作品",
},
]
return (
<div className="max-w-6xl mx-auto space-y-16">
{/* Hero Section */}
<div className="text-center mb-16">
<h1 className="text-4xl md:text-6xl font-light text-gray-900 mb-4"></h1>
<p className="text-lg text-gray-600 max-w-2xl mx-auto"></p>
</div>
{/* Contact Form and Info */}
<div className="grid lg:grid-cols-2 gap-12">
{/* Contact Form */}
<div>
<h2 className="text-2xl font-light text-gray-900 mb-6"></h2>
<Card className="border-0 shadow-lg">
<CardContent className="p-8">
{!isSubmitted ? (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid md:grid-cols-2 gap-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<Input
id="name"
name="name"
value={formData.name}
onChange={handleInputChange}
required
className="w-full"
placeholder="请输入您的姓名"
/>
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<Input
id="phone"
name="phone"
value={formData.phone}
onChange={handleInputChange}
className="w-full"
placeholder="请输入您的电话"
/>
</div>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<Input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleInputChange}
required
className="w-full"
placeholder="请输入您的邮箱"
/>
</div>
<div>
<label htmlFor="service" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<select
id="service"
name="service"
value={formData.service}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-gray-900 focus:border-transparent"
>
<option value=""></option>
<option value="portrait"></option>
<option value="wedding"></option>
<option value="commercial"></option>
<option value="event"></option>
<option value="other"></option>
</select>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<Textarea
id="message"
name="message"
value={formData.message}
onChange={handleInputChange}
required
rows={5}
className="w-full"
placeholder="请详细描述您的拍摄需求、时间、地点等信息..."
/>
</div>
<Button type="submit" disabled={isSubmitting} className="w-full">
{isSubmitting ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
...
</>
) : (
<>
<Send className="h-4 w-4 mr-2" />
</>
)}
</Button>
</form>
) : (
<div className="text-center py-8">
<CheckCircle className="h-16 w-16 text-green-500 mx-auto mb-4" />
<h3 className="text-xl font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-600">24</p>
</div>
)}
</CardContent>
</Card>
</div>
{/* Contact Information */}
<div className="space-y-8">
<div>
<h2 className="text-2xl font-light text-gray-900 mb-6"></h2>
<div className="space-y-4">
{contactInfo.map((info) => (
<div key={info.label} className="flex items-center gap-4">
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center">
<info.icon className="h-5 w-5 text-gray-600" />
</div>
<div>
<div className="text-sm text-gray-500">{info.label}</div>
{info.link ? (
<a
href={info.link}
className="text-gray-900 hover:text-gray-600 transition-colors"
target={info.link.startsWith("http") ? "_blank" : undefined}
rel={info.link.startsWith("http") ? "noopener noreferrer" : undefined}
>
{info.value}
</a>
) : (
<div className="text-gray-900">{info.value}</div>
)}
</div>
</div>
))}
</div>
</div>
{/* Social Links */}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-4"></h3>
<div className="flex gap-4">
{socialLinks.map((social) => (
<a
key={social.label}
href={social.link}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center hover:bg-gray-200 transition-colors"
>
<social.icon className="h-5 w-5 text-gray-600" />
</a>
))}
</div>
</div>
{/* Quick Response */}
<Card className="border-0 shadow-sm bg-blue-50">
<CardContent className="p-6">
<MessageCircle className="h-8 w-8 text-blue-600 mb-3" />
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-600 text-sm">
224
</p>
</CardContent>
</Card>
</div>
</div>
{/* Services */}
<div>
<h2 className="text-3xl font-light text-gray-900 mb-12 text-center"></h2>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{services.map((service) => (
<Card key={service.title} className="border-0 shadow-sm hover:shadow-lg transition-shadow">
<CardContent className="p-6">
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center mb-4">
<service.icon className="h-6 w-6 text-gray-600" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">{service.title}</h3>
<p className="text-gray-600 text-sm mb-3">{service.description}</p>
<div className="text-lg font-medium text-gray-900 mb-4">{service.price}</div>
<div className="space-y-1">
{service.features.map((feature) => (
<div key={feature} className="flex items-center gap-2">
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full"></div>
<span className="text-xs text-gray-600">{feature}</span>
</div>
))}
</div>
</CardContent>
</Card>
))}
</div>
</div>
{/* Workflow */}
<div className="bg-gray-50 rounded-2xl p-8 md:p-12">
<h2 className="text-3xl font-light text-gray-900 mb-12 text-center"></h2>
<div className="grid md:grid-cols-5 gap-6">
{workflowSteps.map((step, index) => (
<div key={step.step} className="text-center">
<div className="w-16 h-16 bg-gray-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 text-lg font-medium">
{step.step}
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">{step.title}</h3>
<p className="text-gray-600 text-sm">{step.description}</p>
{index < workflowSteps.length - 1 && (
<div className="hidden md:block absolute top-8 left-full w-full h-0.5 bg-gray-200 transform translate-x-4"></div>
)}
</div>
))}
</div>
</div>
{/* FAQ */}
<div>
<h2 className="text-3xl font-light text-gray-900 mb-8 text-center"></h2>
<div className="grid md:grid-cols-2 gap-6">
<Card className="border-0 shadow-sm">
<CardContent className="p-6">
<h3 className="text-lg font-medium text-gray-900 mb-3"></h3>
<p className="text-gray-600 text-sm">
2-4
</p>
</CardContent>
</Card>
<Card className="border-0 shadow-sm">
<CardContent className="p-6">
<h3 className="text-lg font-medium text-gray-900 mb-3"></h3>
<p className="text-gray-600 text-sm">
7-14
</p>
</CardContent>
</Card>
<Card className="border-0 shadow-sm">
<CardContent className="p-6">
<h3 className="text-lg font-medium text-gray-900 mb-3"></h3>
<p className="text-gray-600 text-sm">
</p>
</CardContent>
</Card>
<Card className="border-0 shadow-sm">
<CardContent className="p-6">
<h3 className="text-lg font-medium text-gray-900 mb-3"></h3>
<p className="text-gray-600 text-sm">
</p>
</CardContent>
</Card>
</div>
</div>
</div>
)
}