mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-07 16:16:37 +08:00
压缩历史至 95081aff
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { AlertTriangle } from "lucide-react"
|
||||
import { RiskInfo, RiskInfoBox } from "@/components/common/risk/risk-info-box"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
const RISK_BLOCKED_EVENT = "credit-risk-blocked"
|
||||
|
||||
function isRiskInfo(value: unknown): value is RiskInfo {
|
||||
if (!value || typeof value !== "object") return false
|
||||
const riskInfo = value as Partial<RiskInfo>
|
||||
return typeof riskInfo.risk_level === "string" && Array.isArray(riskInfo.risk_labels) && Array.isArray(riskInfo.risks)
|
||||
}
|
||||
|
||||
export function RiskBlockDialog() {
|
||||
const [riskInfo, setRiskInfo] = useState<RiskInfo | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleRiskBlocked = (event: Event) => {
|
||||
const detail = (event as CustomEvent<unknown>).detail
|
||||
if (!isRiskInfo(detail)) return
|
||||
setRiskInfo(detail)
|
||||
}
|
||||
|
||||
window.addEventListener(RISK_BLOCKED_EVENT, handleRiskBlocked)
|
||||
return () => window.removeEventListener(RISK_BLOCKED_EVENT, handleRiskBlocked)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Dialog open={!!riskInfo}>
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
onEscapeKeyDown={(event) => event.preventDefault()}
|
||||
onPointerDownOutside={(event) => event.preventDefault()}
|
||||
onInteractOutside={(event) => event.preventDefault()}
|
||||
className="sm:max-w-md"
|
||||
>
|
||||
<DialogHeader className="items-center text-center sm:text-center">
|
||||
<div className="mb-1 flex size-12 items-center justify-center rounded-full bg-destructive/10">
|
||||
<AlertTriangle className="size-6 text-destructive" />
|
||||
</div>
|
||||
<DialogTitle className="text-center text-lg">账号存在风险</DialogTitle>
|
||||
<DialogDescription className="text-center text-sm leading-6">
|
||||
因触发风控,账号暂时无法使用需登录的功能
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<RiskInfoBox riskInfo={riskInfo} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export interface RiskItem {
|
||||
label: string
|
||||
value?: string
|
||||
desc?: string
|
||||
}
|
||||
|
||||
export interface RiskInfo {
|
||||
risk_level: string
|
||||
risk_labels: string[]
|
||||
risks: RiskItem[]
|
||||
}
|
||||
|
||||
export function RiskInfoBox({
|
||||
riskInfo,
|
||||
label = "风险等级",
|
||||
labelClassName,
|
||||
}: {
|
||||
riskInfo: RiskInfo | null
|
||||
label?: string
|
||||
labelClassName?: string
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-lg border bg-card p-4 shadow-sm">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className={cn("text-sm font-medium text-muted-foreground", labelClassName)}>{label}</span>
|
||||
<Badge variant="destructive">{riskInfo?.risk_level || "未知"}</Badge>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
{riskInfo?.risks.length ? (
|
||||
riskInfo.risks.map(risk => (
|
||||
<div key={risk.value || risk.label} className="rounded-md border bg-muted/35 px-3 py-2.5">
|
||||
<span className="text-sm font-medium leading-5 text-foreground">{risk.label}</span>
|
||||
{risk.desc ? (
|
||||
<p className="mt-1 text-xs leading-5 text-muted-foreground">{risk.desc}</p>
|
||||
) : null}
|
||||
</div>
|
||||
))
|
||||
) : riskInfo?.risk_labels.length ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{riskInfo.risk_labels.map(label => (
|
||||
<Badge key={label} variant="secondary">
|
||||
{label}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">暂无风险标签详情</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { toast } from "sonner"
|
||||
import { X } from "lucide-react"
|
||||
import { RiskInfo, RiskInfoBox } from "@/components/common/risk/risk-info-box"
|
||||
|
||||
const RISK_TOAST_ID = "credit-risk-warning"
|
||||
let isRiskWarningDismissed = false
|
||||
|
||||
function dismissRiskWarningToast(id: string | number) {
|
||||
isRiskWarningDismissed = true
|
||||
toast.dismiss(id)
|
||||
}
|
||||
|
||||
export function showRiskWarningToast(riskInfo: RiskInfo) {
|
||||
if (isRiskWarningDismissed) return
|
||||
|
||||
toast.custom(
|
||||
(id) => (
|
||||
<div className="relative w-[min(calc(100vw-2rem),28rem)]">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="关闭风险提示"
|
||||
className="absolute right-2 top-2 inline-flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
||||
onClick={() => dismissRiskWarningToast(id)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
<RiskInfoBox riskInfo={riskInfo} label="账号存在风险提示" labelClassName="font-semibold text-foreground" />
|
||||
</div>
|
||||
),
|
||||
{
|
||||
id: RISK_TOAST_ID,
|
||||
duration: Infinity,
|
||||
dismissible: true,
|
||||
onDismiss: () => {
|
||||
isRiskWarningDismissed = true
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user