Files
personal/src/components/Contact.tsx
2025-02-27 17:10:33 +08:00

311 lines
9.0 KiB
TypeScript
Raw 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.
import React, { useState } from "react";
import {
Box,
Button,
Container,
Flex,
Heading,
Input,
Stack,
Text,
Textarea,
useToast,
} from "@chakra-ui/react";
import { FaEnvelope, FaPhone, FaMapMarkerAlt } from "react-icons/fa";
import { motion } from "framer-motion";
import axios from "axios";
interface ContactInfoProps {
icon: React.ReactNode;
title: string;
content: string;
}
const ContactInfo = ({ icon, title, content }: ContactInfoProps) => {
return (
<Flex align="center" mb={4}>
<Box color="blue.500" mr={4}>
{icon}
</Box>
<Box>
<Text fontWeight="bold">{title}</Text>
<Text color="gray.600">{content}</Text>
</Box>
</Flex>
);
};
const Contact = () => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [subject, setSubject] = useState("");
const [message, setMessage] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [cooldown, setCooldown] = useState(0);
const toast = useToast();
const API_URL = process.env.REACT_APP_API_URL || "http://localhost:3001";
// 倒计时函数
React.useEffect(() => {
let timer: NodeJS.Timeout | null = null;
if (cooldown > 0) {
timer = setInterval(() => {
setCooldown((prev) => {
if (prev <= 1) {
if (timer) clearInterval(timer);
return 0;
}
return prev - 1;
});
}, 1000);
}
return () => {
if (timer) clearInterval(timer);
};
}, [cooldown]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// 如果在冷却时间内,不允许提交
if (cooldown > 0) {
toast({
title: "请求过于频繁",
description: `请在${cooldown}秒后再试`,
status: "warning",
duration: 3000,
isClosable: true,
position: "top"
});
return;
}
setLoading(true);
setError("");
try {
// 调用后端API发送邮件
const response = await axios.post(`${API_URL}/api/send-email`, {
name,
email,
subject,
message
});
// 显示成功消息
toast({
title: "发送成功",
description: "消息已发送,感谢您的留言,我会尽快回复您!",
status: "success",
duration: 5000,
isClosable: true,
position: "top"
});
// 重置表单
setName("");
setEmail("");
setSubject("");
setMessage("");
} catch (err: any) {
console.error("发送邮件失败:", err);
// 处理速率限制错误
if (err.response?.status === 429) {
const remainingTime = err.response.data.remainingTime || 60;
setCooldown(remainingTime);
toast({
title: "请求过于频繁",
description: `请在${remainingTime}秒后再试`,
status: "warning",
duration: 5000,
isClosable: true,
position: "top"
});
} else {
// 显示其他错误消息
setError(
err.response?.data?.error ||
"发送邮件失败,请稍后再试或直接通过电话联系我"
);
toast({
title: "发送失败",
description: err.response?.data?.error || "发送邮件失败,请稍后再试",
status: "error",
duration: 5000,
isClosable: true,
position: "top"
});
}
} finally {
setLoading(false);
}
};
return (
<Box py={20} id="contact">
<Container maxW="7xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Heading
mb={2}
fontSize={{ base: "3xl", md: "4xl" }}
fontWeight="bold"
textAlign="center"
color="gray.700"
>
</Heading>
<Text
fontSize="xl"
textAlign="center"
mb={10}
color="gray.600"
maxW="3xl"
mx="auto"
>
</Text>
<Flex
direction={{ base: "column", md: "row" }}
gap={8}
bg="white"
borderRadius="lg"
overflow="hidden"
boxShadow="lg"
>
<Box
flex={1}
bg="blue.600"
color="white"
p={{ base: 6, md: 8 }}
position="relative"
overflow="hidden"
>
<Box position="relative" zIndex={2}>
<Heading as="h3" size="lg" mb={6}>
</Heading>
<Stack gap={6}>
<ContactInfo
icon={FaEnvelope({ size: 20 })}
title="邮箱"
content="aisi_verify@163.com"
/>
<ContactInfo
icon={FaPhone({ size: 20 })}
title="电话"
content="+86 185 3901 7628"
/>
<ContactInfo
icon={FaMapMarkerAlt({ size: 20 })}
title="地址"
content="北京市海淀区中关村科技园"
/>
</Stack>
<Text mt={10}>
Java后台开发和大数据项目进行深入交流
</Text>
</Box>
<Box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
bg="blue.700"
opacity={0.5}
zIndex={1}
transform="rotate(-5deg) scale(1.2) translateY(10%)"
/>
</Box>
<Box flex={1.5} p={{ base: 6, md: 8 }}>
<form onSubmit={handleSubmit}>
<Stack gap={4}>
<Box>
<Text fontWeight="bold" mb={1}> *</Text>
<Input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="请输入您的姓名"
borderColor="gray.300"
required
/>
</Box>
<Box>
<Text fontWeight="bold" mb={1}> *</Text>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="请输入您的邮箱"
borderColor="gray.300"
required
/>
</Box>
<Box>
<Text fontWeight="bold" mb={1}></Text>
<Input
type="text"
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder="请输入主题"
borderColor="gray.300"
/>
</Box>
<Box>
<Text fontWeight="bold" mb={1}> *</Text>
<Textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="请输入您的留言内容"
borderColor="gray.300"
height="150px"
required
/>
</Box>
{error && (
<Text color="red.500" fontSize="sm">
{error}
</Text>
)}
{cooldown > 0 && (
<Text color="orange.500" fontSize="sm">
{cooldown}
</Text>
)}
<Button
mt={4}
colorScheme="blue"
type="submit"
isLoading={loading}
loadingText="发送中..."
size="lg"
fontWeight="normal"
w={{ base: "full", md: "auto" }}
isDisabled={cooldown > 0}
>
{cooldown > 0 ? `请等待 ${cooldown}` : (loading ? "发送中..." : "发送消息")}
</Button>
</Stack>
</form>
</Box>
</Flex>
</motion.div>
</Container>
</Box>
);
};
export default Contact;