diff --git a/.gitignore b/.gitignore index 894a840f..ce82f3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,9 @@ frontend/node_modules/* frontend/.next/* frontend/next-env.d.ts frontend/package-lock.json -frontend/.env +.env.* +!.env.example +*.tsbuildinfo # os .DS_Store diff --git a/frontend/components/common/receive/ReceiveContent.tsx b/frontend/components/common/receive/ReceiveContent.tsx index ee5c3887..8ad2dfcc 100644 --- a/frontend/components/common/receive/ReceiveContent.tsx +++ b/frontend/components/common/receive/ReceiveContent.tsx @@ -8,6 +8,7 @@ import {Badge} from '@/components/ui/badge'; import {TRUST_LEVEL_OPTIONS} from '@/components/common/project'; import {ArrowLeftIcon, Copy, Tag, Gift, Clock, AlertCircle, Package} from 'lucide-react'; import ContentRender from '@/components/common/markdown/ContentRender'; +import {ReportButton} from '@/components/common/receive/ReportButton'; import services from '@/lib/services'; import {BasicUserInfo} from '@/lib/services/core'; import {GetProjectResponseData} from '@/lib/services/project'; @@ -331,6 +332,18 @@ export function ReceiveContent({data}: ReceiveContentProps) { /> + + +
+
+ +
+
); } diff --git a/frontend/components/common/receive/ReportButton.tsx b/frontend/components/common/receive/ReportButton.tsx new file mode 100644 index 00000000..7ee5ef62 --- /dev/null +++ b/frontend/components/common/receive/ReportButton.tsx @@ -0,0 +1,160 @@ +'use client'; + +import {useState} from 'react'; +import {toast} from 'sonner'; +import {Button} from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import {Textarea} from '@/components/ui/textarea'; +import {Label} from '@/components/ui/label'; +import {Flag} from 'lucide-react'; +import services from '@/lib/services'; +import {BasicUserInfo} from '@/lib/services/core'; + +/** + * 举报按钮组件 Props + */ +interface ReportButtonProps { + /** 项目ID */ + projectId: string; + /** 当前用户信息 */ + user: BasicUserInfo | null; + /** 是否已经举报过 */ + hasReported?: boolean; + /** 按钮样式变体 */ + variant?: 'default' | 'text'; +} + +/** + * 举报按钮组件 + * 提供项目举报功能,通过弹窗让用户填写举报理由 + */ +export function ReportButton({projectId, user, hasReported = false, variant = 'default'}: ReportButtonProps) { + const [isOpen, setIsOpen] = useState(false); + const [reason, setReason] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [hasReportedLocal, setHasReportedLocal] = useState(hasReported); + + // 计算 trimmed reason,用于多个地方的验证 + const trimmedReason = reason.trim(); + + /** + * 处理举报提交 + */ + const handleSubmit = async () => { + if (!trimmedReason) { + toast.error('请填写举报理由'); + return; + } + + if (trimmedReason.length > 255) { + toast.error('举报理由不能超过255个字符'); + return; + } + + try { + setIsSubmitting(true); + + const result = await services.project.reportProjectSafe(projectId, trimmedReason); + + if (result.success) { + toast.success('举报提交成功,感谢您的反馈'); + setHasReportedLocal(true); + setIsOpen(false); + setReason(''); + } else { + // 检查是否是重复举报的错误 + if (result.error && result.error.includes('已举报过当前项目')) { + setHasReportedLocal(true); + } + toast.error(result.error || '举报失败,请稍后重试'); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '举报失败,请稍后重试'; + // 检查是否是重复举报的错误 + if (errorMessage.includes('已举报过当前项目')) { + setHasReportedLocal(true); + } + toast.error(errorMessage); + } finally { + setIsSubmitting(false); + } + }; + + /** + * 处理对话框关闭 + */ + const handleClose = () => { + if (!isSubmitting) { + setIsOpen(false); + setReason(''); + } + }; + + return ( + + + + + + + 举报项目 + + 如果您发现此项目存在违规内容、恶意行为或其他问题,请填写举报理由。我们会认真处理您的反馈。 + + +
+
+ +