"use client" import * as React from "react" import { useState } from "react" import { Button } from "@/components/ui/button" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { Spinner } from "@/components/ui/spinner" import { AlertTriangle, X, Layers, Banknote, TicketSlash } from "lucide-react" import { formatDateTime } from "@/lib/utils" import type { Order, DisputeWithOrder, Dispute } from "@/lib/services" import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" import { DisputeService } from "@/lib/services" import { toast } from "sonner" import { useIsMobile } from "@/hooks/use-mobile" import { usePublicConfig } from "@/hooks/use-public-config" import { useTransaction } from "@/contexts/transaction-context" import { useUser } from "@/contexts/user-context" import { Textarea } from "@/components/ui/textarea" import { typeConfig } from "@/components/common/general/table-filter" import { VisuallyHidden } from "@radix-ui/react-visually-hidden" type DisputeViewer = 'payer' | 'payee' /** * 活动详情弹窗 */ export function OrderDetailDialog({ order }: { order: Order }) { const [open, setOpen] = useState(false) return ( <>

查看详情

积分流转详情 查看积分活动的详细信息

{order.app_name || 'RECEIPT'}

{formatDateTime(order.created_at)}

Total Credits

{parseFloat(order.amount).toFixed(2)}
编号 {order.order_no}
类型 {typeConfig[order.type]?.label || order.type}
消费方 {order.payer_username}
服务方 {order.payee_username}
备注 {order.remark}
{(order.status === 'success' || order.status === 'refund') && (
时间 {formatDateTime(order.trade_time)}
)}
{[...Array(30)].map((_, i) => (
))}

LINUX DO Credit

) } /** * 发起争议弹窗 */ export function CreateDisputeDialog({ order, onSuccess }: { order: Order; onSuccess?: () => void }) { const isMobile = useIsMobile() const { config: publicConfig, loading: configLoading, error: configError } = usePublicConfig() const { updateOrderStatus } = useTransaction() const [open, setOpen] = useState(false) const [loading, setLoading] = useState(false) const [reason, setReason] = useState("") const resetForm = () => { setReason("") } const handleButtonClick = async () => { if (configLoading) { try { await new Promise(resolve => setTimeout(resolve, 100)) if (configLoading) { toast.error('配置加载中', { description: '请稍候再试' }) return } } catch { toast.error('配置加载失败', { description: '请刷新页面重试' }) return } } if (configError || !publicConfig) { toast.error('无法获取配置', { description: configError?.message || '请刷新页面重试' }) return } const tradeTime = new Date(order.trade_time) const expiryTime = new Date(tradeTime.getTime() + publicConfig.dispute_time_window_hours * 60 * 60 * 1000) const now = new Date() if (now > expiryTime) { toast.error('无法发起争议', { description: '已超过最晚发起争议时间' }) return } setOpen(true) } const handleOpenChange = (newOpen: boolean) => { if (newOpen && !loading) { resetForm() } setOpen(newOpen) } const handleCreateDispute = async () => { if (!reason.trim()) { toast.error('表单验证失败', { description: '请填写争议原因' }) return } if (reason.length > 100) { toast.error('表单验证失败', { description: '争议原因不能超过 100 个字符' }) return } try { setLoading(true) const dispute = await DisputeService.createDispute({ order_id: order.id, reason: reason.trim() }) toast.success('争议已发起', { description: '请等待服务方处理' }) updateOrderStatus(order.id, { status: 'disputing', dispute_id: dispute.id }) setOpen(false) onSuccess?.() } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : '发起争议失败' toast.error('发起争议失败', { description: errorMessage }) } finally { setLoading(false) } } return ( <>

发起争议

发起争议 如果您对该积分活动有疑问,可以发起争议,服务方将在规定时间内处理。