410 lines
12 KiB
TypeScript
410 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import { Loading } from "@/components/dashboard/loading";
|
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { toast } from "@/components/ui/toaster";
|
|
// import { trpc } from "@/lib/trpc/client";
|
|
// import { PostHogEvent } from "@/providers/PostHogProvider";
|
|
// import { type Workspace } from "@aigxion/db";
|
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
import React, { useEffect, useState } from "react";
|
|
|
|
// import { getTenantId } from "@/lib/auth";
|
|
// import { db } from "@/lib/db";
|
|
import { ArrowLeft } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { ChangePlanButton } from "./button";
|
|
import { PayInfo, PaymentInfo, PaymentOrder, payStaff, queryPayStatus, queryProductionList } from "@/lib/http/staff";
|
|
import { FadeIn } from "@/components/landing/fade-in";
|
|
import { cn } from "@/lib/utils";
|
|
import { Modal, QRCode, message } from "antd";
|
|
import Image from "next/image";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
type Props = {
|
|
newPlan: "free" | "pro";
|
|
// workspace: Workspace;
|
|
tier: string;
|
|
};
|
|
|
|
interface ProductInfo {
|
|
[key: string]: {
|
|
id: number;
|
|
name: string;
|
|
href: string;
|
|
price: number;
|
|
description: string;
|
|
buttonText: string;
|
|
features: string[];
|
|
footnotes: never[];
|
|
};
|
|
}
|
|
|
|
const tiers: ProductInfo = {
|
|
|
|
free: {
|
|
id: 1,
|
|
name: "月度计划",
|
|
href: "/manage",
|
|
price: 299,
|
|
description: "启动下一个 API 所需的一切!",
|
|
buttonText: "基础",
|
|
features: [
|
|
"100 个每月活动密钥",
|
|
"每月 2500 次成功验证",
|
|
"无限的API",
|
|
"7天免费保留",
|
|
],
|
|
footnotes: [],
|
|
},
|
|
|
|
pro: {
|
|
id: 2,
|
|
name: "季度计划",
|
|
href: "/manage",
|
|
price: 699,
|
|
description: "对于那些有团队和更苛刻需求的人",
|
|
buttonText: "Pro",
|
|
features: [
|
|
"250 每月 xxxxxxxx*",
|
|
"150,000 成功 xxxxxxx 包括 **",
|
|
"xxxxxxxx APIs",
|
|
"与团队成员的工作空间",
|
|
"90 天分析保留",
|
|
"90 天审核日志保留",
|
|
],
|
|
footnotes: [
|
|
// " * Additional active keys are billed at $0.10",
|
|
// " ** Additional verifications are billed at $10 per 100,000",
|
|
],
|
|
},
|
|
|
|
custom: {
|
|
id: 3,
|
|
name: "年度计划",
|
|
href: "/manage",
|
|
price: 1299,
|
|
description: "我们为有批量需求的人提供定制定价",
|
|
buttonText: "Go",
|
|
features: [
|
|
"自定义验证限制",
|
|
"自定义活动键限制",
|
|
"自定义分析保留",
|
|
"专用支持合同",
|
|
"每个API的白名单IP",
|
|
],
|
|
footnotes: [],
|
|
},
|
|
};
|
|
|
|
export const PlanCard: React.FC<Props> = ({ tier = "free", newPlan = "free" }) => {
|
|
const router = useRouter();
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [payQrcode, setPayQrcode] = useState("");
|
|
|
|
const path = usePathname()
|
|
const { t, i18n } = useTranslation()
|
|
|
|
const searchParams = useSearchParams()
|
|
const staffId = searchParams.get('staffId')
|
|
|
|
console.log("---PlanCard----", staffId, path)
|
|
|
|
const isChatPage = path.includes('chat')
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
// console.log("------------email", infoRef.current, userData.auth_token)
|
|
|
|
async function initFunc() {
|
|
const data = await queryProductionList()
|
|
console.log(data)
|
|
|
|
}
|
|
initFunc()
|
|
|
|
}, []);
|
|
|
|
// const changePlan = trpc.workspace.changePlan.useMutation({
|
|
// onSuccess: (data, variables, _context) => {
|
|
// toast.success(data.title, {
|
|
// description: data.message,
|
|
// });
|
|
// PostHogEvent({
|
|
// name: "plan_changed",
|
|
// properties: { plan: variables.plan, workspace: variables.workspaceId },
|
|
// });
|
|
// setOpen(false);
|
|
// router.refresh();
|
|
// },
|
|
// onError: (error) => {
|
|
// toast.error(error.message);
|
|
// },
|
|
// });
|
|
|
|
|
|
|
|
async function pollOrder(orderId: string, interval: number, maxAttempts: number): Promise<PaymentOrder | null> {
|
|
let attempts = 0;
|
|
|
|
console.log("开始轮训订单", orderId)
|
|
|
|
const poll = async (): Promise<PaymentOrder | null> => {
|
|
|
|
try {
|
|
// 发送订单查询请求,假设返回的数据结构为 Order
|
|
const result = await queryPayStatus(orderId);
|
|
const order = result.data as PaymentOrder;
|
|
|
|
// 如果订单状态为已完成,或者达到最大尝试次数,则停止轮询并返回订单
|
|
if (order.state >= 2 || attempts >= maxAttempts) {
|
|
return order;
|
|
}
|
|
|
|
// 增加尝试次数
|
|
attempts++;
|
|
|
|
// 等待一段时间后继续轮询
|
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
|
|
// 递归调用自身,继续轮询
|
|
return poll();
|
|
} catch (error) {
|
|
// 处理轮询过程中的错误
|
|
console.error("Error occurred while polling order:", error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
return poll();
|
|
}
|
|
|
|
const isSamePlan = true;
|
|
return (
|
|
<div
|
|
key={tier}
|
|
className={
|
|
"border border-border flex w-full flex-col justify-between rounded-lg bg-white dark:bg-black p-8 lg:w-1/3 xl:p-10 "
|
|
}
|
|
>
|
|
<div className="flex items-center justify-between gap-x-4">
|
|
<h2 id={tier} className={"text-content text-2xl font-semibold leading-8"}>
|
|
{tiers[tier].name}
|
|
</h2>
|
|
</div>
|
|
<p className="mt-4 min-h-[3rem] text-sm leading-6 text-content-subtle">
|
|
{tiers[tier].description}
|
|
</p>
|
|
<p className="flex items-center mx-auto mt-6 gap-x-1">
|
|
{typeof tiers[tier].price === "number" ? (
|
|
<>
|
|
<span className="text-4xl font-bold tracking-tight text-center text-content">
|
|
{`${tiers[tier].price}`}
|
|
</span>
|
|
<span className="mx-auto text-sm font-semibold leading-6 text-center text-content-subtle">
|
|
{"/month"}
|
|
</span>
|
|
</>
|
|
) : (
|
|
<span className="mx-auto text-4xl font-bold tracking-tight text-center text-content">
|
|
{tiers[tier].price}
|
|
</span>
|
|
)}
|
|
</p>
|
|
|
|
<div className="flex flex-col justify-between grow">
|
|
<ul className="mt-8 space-y-3 text-sm leading-6 text-content-subtle xl:mt-10">
|
|
{tiers[tier].features.map((feature) => (
|
|
<li key={feature} className="flex gap-x-3">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24"
|
|
className="flex-none w-5 h-6 text-gray-700"
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
fill="currentColor"
|
|
fillRule="evenodd"
|
|
d="M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353l8.493-12.739a.75.75 0 0 1 1.04-.208Z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
|
|
{feature}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
{tiers[tier].footnotes && (
|
|
<ul className="mt-6 mb-8">
|
|
{tiers[tier].footnotes.map((footnote, i) => (
|
|
<li
|
|
// biome-ignore lint/suspicious/noArrayIndexKey: I got nothing better right now
|
|
key={`note-${i}`}
|
|
className="flex text-xs text-content-subtle gap-x-3"
|
|
>
|
|
{footnote}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
{/* {tier === "custom" ? (
|
|
<Link href="mailto:support@unkey.dev">
|
|
<Button
|
|
className="w-full col-span-1"
|
|
// variant={workspace.plan === "enterprise" ? "disabled" : "primary"}
|
|
// disabled={workspace.plan === "enterprise"}
|
|
>
|
|
|
|
Go
|
|
</Button>
|
|
</Link>
|
|
) : (
|
|
<ChangePlanButton
|
|
// workspace={workspace}
|
|
newPlan={newPlan}
|
|
label={tiers[tier].buttonText}
|
|
/>
|
|
)} */}
|
|
|
|
|
|
|
|
{/* <Button
|
|
className="w-full col-span-1"
|
|
// variant={workspace.plan === "enterprise" ? "disabled" : "primary"}
|
|
// disabled={workspace.plan === "enterprise"}
|
|
>
|
|
|
|
Go
|
|
</Button> */}
|
|
|
|
{
|
|
staffId && <Button
|
|
disabled={isLoading}
|
|
className="mt-4 "
|
|
type="submit"
|
|
|
|
onClick={async (e) => {
|
|
e.preventDefault()
|
|
setIsLoading(true)
|
|
// setTimeout(() => {
|
|
// setOpen(true)
|
|
// setIsLoading(false)
|
|
// }, 2000)
|
|
if (!!Number(staffId)) {
|
|
const result = await payStaff({
|
|
id: Number(staffId),
|
|
production_id: tiers[tier].id,
|
|
pay_type: "alipay",
|
|
} as PayInfo)
|
|
|
|
|
|
const typedData = result.data as PaymentInfo;
|
|
|
|
|
|
console.log("----payStaff---", typedData)
|
|
|
|
setPayQrcode(typedData.pay_url)
|
|
|
|
setOpen(true)
|
|
|
|
// 启动轮训
|
|
setTimeout(async () => {
|
|
await pollOrder(typedData.trade_no, 2000, 60)
|
|
|
|
router.push(`/manage/staffs`);
|
|
})
|
|
|
|
|
|
} else {
|
|
message.warning("无效员工ID")
|
|
}
|
|
|
|
|
|
}}
|
|
>
|
|
{isLoading ? <Loading /> : "Pay"}
|
|
</Button>
|
|
}
|
|
|
|
</div>
|
|
|
|
|
|
<Modal
|
|
// title="Modal 1000px width"
|
|
centered
|
|
open={open}
|
|
onOk={() => {
|
|
if (setOpen) {
|
|
setOpen(false)
|
|
}
|
|
}}
|
|
onCancel={() => {
|
|
setOpen(false)
|
|
}}
|
|
footer=""
|
|
// width={"50%"}
|
|
|
|
className={cn('min-w-[95%] lg:min-w-[700px] p-[20px]', "")}
|
|
>
|
|
|
|
<div className="mx-auto sm:max-w-2xl min-h-[500px] mb-[3rem] lg:mb-[5rem] rounded bg-[#fff]">
|
|
|
|
<FadeIn >
|
|
<h3 className='text-center px-6 sm:px-4 text-2xl font-bold py-[1.2rem]'>请使用支付宝进行支付</h3>
|
|
|
|
{/* <div className='flex flex-row items-center justify-around bg-[#F5F5F5] text-[#666666] mb-[2rem]'>
|
|
<Link href="/xauth/login" >
|
|
<Button className="h-8 " >Login</Button>
|
|
</Link>
|
|
<Link href="/xauth/login">
|
|
<Button className="h-8" >Signup</Button>
|
|
</Link>
|
|
<Link href="/xauth/login">
|
|
<Button className="h-8" >Download</Button>
|
|
</Link>
|
|
</div> */}
|
|
|
|
{payQrcode && <div className="rounded bg-clip-border overflow-clip">
|
|
{/* <figure
|
|
className='w-full'
|
|
><Image fill={true} src={payQrcode} className='w-full' alt="Hero Image" /></figure> */}
|
|
<QRCode
|
|
value={payQrcode}
|
|
size={200} // 二维码的大小
|
|
style={{ margin: 'auto' }}
|
|
imageSettings={{ // 二维码中间的logo图片
|
|
src: 'logoUrl',
|
|
height: 100,
|
|
width: 100,
|
|
excavate: true, // 中间图片所在的位置是否镂空
|
|
}}
|
|
/>
|
|
|
|
|
|
{/* <figure
|
|
className="w-full min-h-[146px] lg:min-h-[246px] "
|
|
// style={{ width: '100%', minHeight: "246px" }}
|
|
>
|
|
<img src={payQrcode} alt="Hero Image" style={{ width: '100%', marginBottom: '8px' }} />
|
|
</figure> */}
|
|
</div>}
|
|
|
|
</FadeIn>
|
|
</div>
|
|
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|