import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { motion, useScroll, useTransform } from 'framer-motion'; import { ArrowLeftOutlined, ShareAltOutlined, CalendarOutlined, ClockCircleOutlined, EnvironmentOutlined, UserOutlined, UploadOutlined, PayCircleOutlined } from '@ant-design/icons'; import confetti from 'canvas-confetti'; import { message, Spin, Button, Result, Modal, Form, Input, Select, Radio, Checkbox, Upload } from 'antd'; import { getActivityDetail, signUpActivity, queryOrderStatus } from '../../api'; import styles from '../../components/activity/activity.module.less'; import { pageTransition, buttonTap } from '../../animation'; import LoginModal from '../../components/LoginModal'; import { useAuth } from '../../context/AuthContext'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { QRCodeSVG } from 'qrcode.react'; const ActivityDetail = () => { const { id } = useParams(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { scrollY } = useScroll(); const { login, user } = useAuth(); const [loginVisible, setLoginVisible] = useState(false); const [signupFormVisible, setSignupFormVisible] = useState(false); const [paymentModalVisible, setPaymentModalVisible] = useState(false); const [paymentInfo, setPaymentInfo] = useState(null); const [form] = Form.useForm(); // Header animation: transparent to white with shadow const headerBg = useTransform(scrollY, [0, 60], ['rgba(255,255,255,0)', 'rgba(255,255,255,1)']); const headerShadow = useTransform(scrollY, [0, 60], ['none', '0 2px 8px rgba(0,0,0,0.1)']); const headerColor = useTransform(scrollY, [0, 60], ['rgba(255,255,255,1)', 'rgba(0,0,0,0.85)']); const titleOpacity = useTransform(scrollY, [100, 200], [0, 1]); const { data: activity, isLoading, error, refetch } = useQuery({ queryKey: ['activity', id], queryFn: async () => { try { const res = await getActivityDetail(id); return res.data; } catch (err) { throw new Error(err.response?.data?.detail || 'Failed to load activity'); } }, staleTime: 0, // Ensure fresh data refetchOnMount: 'always', // Force refetch on mount }); //// / // Force a refresh if needed (as requested by user) useEffect(() => { // 1. Force React Query refetch refetch(); // 2. Hard refresh logic after 1 second delay const timer = setTimeout(() => { const hasRefreshedKey = `has_refreshed_activity_${id}`; if (!sessionStorage.getItem(hasRefreshedKey)) { sessionStorage.setItem(hasRefreshedKey, 'true'); window.location.reload(); } }, 0); return () => clearTimeout(timer); }, [id, refetch]); // Auto-fill form fields when the signup form becomes visible useEffect(() => { if (signupFormVisible && user && activity?.signup_form_config) { const initialValues = {}; activity.signup_form_config.forEach(field => { // Auto-fill phone number if (field.name === 'phone' && user.phone_number) { initialValues[field.name] = user.phone_number; } // Auto-fill name (nickname) if the field name is 'name' if (field.name === 'name' && user.nickname) { initialValues[field.name] = user.nickname; } }); if (Object.keys(initialValues).length > 0) { form.setFieldsValue(initialValues); } } }, [signupFormVisible, user, activity, form]); const signUpMutation = useMutation({ mutationFn: (values) => signUpActivity(id, { signup_info: values || {} }), onSuccess: (data) => { // 检查是否需要支付 if (data.payment_required) { setPaymentInfo(data); // 先关闭报名表单,确保层级正确 setSignupFormVisible(false); // 延迟一点点时间打开支付弹窗,避免状态更新冲突 setTimeout(() => { setPaymentModalVisible(true); }, 300); // 不再显示 message,避免遮挡 return; } message.success('报名成功!'); setSignupFormVisible(false); confetti({ particleCount: 150, spread: 70, origin: { y: 0.6 }, colors: ['#00b96b', '#1890ff', '#ffffff'] }); queryClient.invalidateQueries(['activity', id]); queryClient.invalidateQueries(['activities']); }, onError: (err) => { message.error(err.response?.data?.detail || err.response?.data?.error || '报名失败,请稍后重试'); } }); // Polling for payment status useEffect(() => { let timer; if (paymentModalVisible && paymentInfo?.order_id) { timer = setInterval(async () => { try { const response = await queryOrderStatus(paymentInfo.order_id); if (response.data.status === 'paid') { message.success('支付成功,报名已确认!'); setPaymentModalVisible(false); setPaymentInfo(null); // Trigger success effects confetti({ particleCount: 150, spread: 70, origin: { y: 0.6 }, colors: ['#00b96b', '#1890ff', '#ffffff'] }); queryClient.invalidateQueries(['activity', id]); queryClient.invalidateQueries(['activities']); clearInterval(timer); } } catch (error) { // ignore error during polling } }, 3000); } return () => clearInterval(timer); }, [paymentModalVisible, paymentInfo, id, queryClient]); const handleShare = async () => { const url = window.location.href; if (navigator.share) { try { await navigator.share({ title: activity?.title, text: '来看看这个精彩活动!', url: url }); } catch (err) { console.log('Share canceled'); } } else { navigator.clipboard.writeText(url); message.success('链接已复制到剪贴板'); } }; const handleSignUp = () => { if (!localStorage.getItem('token')) { message.warning('请先登录后报名'); setLoginVisible(true); return; } // Check if we need to collect info if (activity.signup_form_config && activity.signup_form_config.length > 0) { setSignupFormVisible(true); } else { // Direct signup if no info needed signUpMutation.mutate({}); } }; const handleFormSubmit = (values) => { // Handle file uploads if any (convert to base64 or just warn if not supported) // For now, we just pass values. // Note: File objects won't serialize to JSON well. signUpMutation.mutate(values); }; const normFile = (e) => { if (Array.isArray(e)) { return e; } return e?.fileList; }; if (isLoading) { return (
); } if (error) { return (
navigate(-1)}> 返回列表 ]} />
); } const cleanUrl = (url) => { if (!url) return ''; return url.replace(/[`\s]/g, ''); }; return ( {/* Sticky Header */} navigate(-1)} style={{ cursor: 'pointer', color: headerColor, fontSize: 20 }} > {activity.title} {/* Hero Image with LayoutId for shared transition */}
{/* Content */}

{activity.title}

{activity.start_time ? new Date(activity.start_time).toLocaleDateString() : 'TBD'}
{activity.start_time ? new Date(activity.start_time).toLocaleTimeString() : 'TBD'}
{activity.location || '线上活动'}
{activity.current_signups || 0} / {activity.max_participants} 已报名
{activity.is_paid ? `¥${activity.price}` : '免费'}
{activity.status || (new Date() < new Date(activity.start_time) ? '报名中' : '已结束')}

活动详情

{String(children).replace(/\n$/, '')} ) : ( {children} ) } }} > {activity.description || activity.content || '暂无详情描述'}
{/* Fixed Footer */}
距离报名截止 {/* Simple countdown placeholder */} 3天 12小时
{signUpMutation.isPending ? '提交中...' : activity.is_signed_up ? '已报名' : '立即报名'}
setLoginVisible(false)} onLoginSuccess={(userData) => { login(userData); // Auto trigger signup after login if needed, or just let user click again }} /> setSignupFormVisible(false)} onOk={form.submit} okText={activity?.is_paid && activity?.price > 0 ? `支付 ¥${activity.price}` : '提交报名'} confirmLoading={signUpMutation.isPending} destroyOnHidden >
{activity.signup_form_config && activity.signup_form_config.map(field => { let inputNode; const commonProps = { placeholder: field.placeholder || `请输入${field.label}`, }; switch (field.type) { case 'select': inputNode = ( ); break; case 'radio': inputNode = ( {field.options?.map(opt => ( {opt.label} ))} ); break; case 'checkbox': inputNode = ( {field.options?.map(opt => ( {opt.label} ))} ); break; case 'textarea': inputNode = ; break; case 'file': inputNode = ( false} maxCount={1}> ); break; default: inputNode = ; } const itemProps = { key: field.name, name: field.name, label: field.label, rules: [{ required: field.required, message: `请填写${field.label}` }], }; if (field.type === 'file') { itemProps.valuePropName = 'fileList'; itemProps.getValueFromEvent = normFile; } return ( {inputNode} ); })}
setPaymentModalVisible(false)} footer={null} destroyOnHidden width={360} zIndex={1001} // 确保层级高于其他弹窗 >
{paymentInfo?.code_url ? ( <>

¥{paymentInfo.price}

请使用微信扫一扫支付

) : ( )}
); }; export default ActivityDetail;