init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
117
webapp/components/points/ReferralCodeCard.tsx
Normal file
117
webapp/components/points/ReferralCodeCard.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Copy } from "lucide-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Button } from "@heroui/react";
|
||||
import Image from "next/image";
|
||||
import BorderedButton from "@/components/common/BorderedButton";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
interface ReferralCodeCardProps {
|
||||
code: string;
|
||||
usedCount?: number;
|
||||
loading?: boolean;
|
||||
onCopy?: () => void;
|
||||
onShare?: () => void;
|
||||
}
|
||||
|
||||
export default function ReferralCodeCard({
|
||||
code = "PR0T0-8821",
|
||||
usedCount = 0,
|
||||
loading = false,
|
||||
onCopy,
|
||||
onShare,
|
||||
}: ReferralCodeCardProps) {
|
||||
const { t } = useApp();
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopyClick = () => {
|
||||
onCopy?.();
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-[32] bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8 flex flex-col gap-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 h-6">
|
||||
<Image src="/components/card/icon0.svg" alt="" width={24} height={24} />
|
||||
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%]">
|
||||
{t("points.referralCode")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-caption-tiny font-regular text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
|
||||
{t("points.shareYourCodeDescription").replace("{count}", usedCount.toString())}
|
||||
</p>
|
||||
|
||||
{/* Code Display and Buttons */}
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Code Display Row */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Code Container */}
|
||||
<div className="flex-1 bg-bg-subtle dark:bg-gray-700 rounded-xl border border-border-gray dark:border-gray-600 px-4 py-3 h-[46px] flex items-center">
|
||||
{loading ? (
|
||||
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-24 animate-pulse" />
|
||||
) : (
|
||||
<span className="text-body-default font-bold text-text-primary dark:text-white leading-[150%] font-inter">
|
||||
{code === "Loading..." ? t("points.loading") : code}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Copy Button */}
|
||||
<Button
|
||||
isIconOnly
|
||||
onClick={handleCopyClick}
|
||||
className="rounded-xl w-[46px] h-[46px] min-w-[46px] bg-text-primary dark:bg-white"
|
||||
>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{copied ? (
|
||||
<motion.svg
|
||||
key="check"
|
||||
width="20" height="20" viewBox="0 0 24 24"
|
||||
fill="none" stroke="#22c55e" strokeWidth="2.5"
|
||||
strokeLinecap="round" strokeLinejoin="round"
|
||||
initial={{ opacity: 0, scale: 0.7 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.7 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<motion.path
|
||||
d="M4 12 L10 18 L20 7"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
/>
|
||||
</motion.svg>
|
||||
) : (
|
||||
<motion.span
|
||||
key="copy"
|
||||
initial={{ opacity: 0, scale: 0.7 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.7 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
>
|
||||
<Copy size={20} className="text-white dark:text-black" />
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Share Button */}
|
||||
<BorderedButton
|
||||
size="lg"
|
||||
fullWidth
|
||||
onClick={onShare}
|
||||
isTheme
|
||||
>
|
||||
{t("points.share")}
|
||||
</BorderedButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user