From de395be400043370780d1eabe0d4e21e3edce97f Mon Sep 17 00:00:00 2001
From: small-lovely-cat <77799160+small-lovely-cat@users.noreply.github.com>
Date: Sun, 4 Oct 2026 03:28:49 +0000
Subject: [PATCH 1/4] feat(redenvelope): add risk control for red envelope
claim and add QR Code Sharing
---
.../common/redenvelope/red-envelope-card.tsx | 2 +-
.../common/redenvelope/red-envelope-share.tsx | 70 +++++++++++
.../components/common/trade/red-envelope.tsx | 24 +++-
frontend/package.json | 1 +
frontend/pnpm-lock.yaml | 12 ++
frontend/public/red-envelope-scan.svg | 54 ++++++++
internal/apps/dashboard/routers.go | 4 +-
internal/apps/oauth/risk.go | 6 +-
internal/apps/redenvelope/risk.go | 116 ++++++++++++++++++
internal/apps/redenvelope/routers.go | 30 ++++-
internal/common/errs.go | 1 +
internal/db/migrator/migrator.go | 10 ++
internal/db/redis.go | 39 +++++-
internal/model/system_configs.go | 32 ++---
14 files changed, 370 insertions(+), 31 deletions(-)
create mode 100644 frontend/components/common/redenvelope/red-envelope-share.tsx
create mode 100644 frontend/public/red-envelope-scan.svg
create mode 100644 internal/apps/redenvelope/risk.go
diff --git a/frontend/components/common/redenvelope/red-envelope-card.tsx b/frontend/components/common/redenvelope/red-envelope-card.tsx
index b3903e49..eb1706dc 100644
--- a/frontend/components/common/redenvelope/red-envelope-card.tsx
+++ b/frontend/components/common/redenvelope/red-envelope-card.tsx
@@ -118,7 +118,7 @@ export function RedEnvelopeCard({
animate={status === "opening" ? {
scale: [1, 0.92, 0],
opacity: [1, 0]
- } : {}}
+ } : { scale: 1, opacity: 1 }}
transition={status === "opening" ? {
duration: 0.55,
ease: "easeInOut"
diff --git a/frontend/components/common/redenvelope/red-envelope-share.tsx b/frontend/components/common/redenvelope/red-envelope-share.tsx
new file mode 100644
index 00000000..b4d87437
--- /dev/null
+++ b/frontend/components/common/redenvelope/red-envelope-share.tsx
@@ -0,0 +1,70 @@
+"use client"
+
+import { useState } from "react"
+import Image from "next/image"
+import { motion, useReducedMotion } from "motion/react"
+import { QRCodeSVG } from "qrcode.react"
+
+export function RedEnvelopeShare({ link }: { link: string }) {
+ const [playing, setPlaying] = useState(false)
+ const [replay, setReplay] = useState(0)
+ const reduceMotion = useReducedMotion()
+
+ return (
+ <>
+
+ {
+ if (event.pointerType === "mouse") setPlaying(true)
+ }}
+ onPointerLeave={(event) => {
+ if (event.pointerType === "mouse") setPlaying(false)
+ }}
+ onFocus={() => setPlaying(true)}
+ onBlur={(event) => {
+ if (!event.currentTarget.contains(event.relatedTarget)) setPlaying(false)
+ }}
+ >
+ {
+ setPlaying(true)
+ setReplay(value => value + 1)
+ }}
+ >
+
+
+
+
+ {playing && (
+
+ )}
+
+
+ >
+ )
+}
diff --git a/frontend/components/common/trade/red-envelope.tsx b/frontend/components/common/trade/red-envelope.tsx
index f7527297..875767f3 100644
--- a/frontend/components/common/trade/red-envelope.tsx
+++ b/frontend/components/common/trade/red-envelope.tsx
@@ -4,7 +4,7 @@ import * as React from "react"
import { useState, useEffect } from "react"
import Image from "next/image"
import { toast } from "sonner"
-import { Gift, Copy, Check, ExternalLink, Pencil, X, ImagePlus, History } from "lucide-react"
+import { Gift, Copy, Check, Share2, Pencil, X, ImagePlus, History } from "lucide-react"
import type { UploadImageResponse } from "@/lib/services"
import { getFileUrl } from "@/lib/services/upload/upload.service"
import { Input } from "@/components/ui/input"
@@ -18,6 +18,7 @@ import { ListData } from "@/components/common/general/list-data"
import { ImageCropper } from "@/components/common/redenvelope/image-cropper"
import { motion } from "motion/react"
import { RedEnvelopeCard } from "@/components/common/redenvelope/red-envelope-card"
+import { RedEnvelopeShare } from "@/components/common/redenvelope/red-envelope-share"
import { useUser } from "@/contexts/user-context"
import services from "@/lib/services"
import type { RedEnvelopeType, CreateRedEnvelopeRequest, PublicConfigResponse, RedEnvelope, RedEnvelopeListResponse } from "@/lib/services"
@@ -696,7 +697,7 @@ export function RedEnvelope({ onSuccess }: { onSuccess?: () => void }) {
-
+
+ {isResultOpen && resultLink &&
}
+