Merge branch 'main' of https://github.com/OmnTeam/ppanel-frontend
This commit is contained in:
commit
0a57896d87
@ -31,5 +31,13 @@
|
||||
"subscriptionUrl": "Subscription URL",
|
||||
"totalTraffic": "Total Traffic",
|
||||
"unknown": "Unknown",
|
||||
"used": "Used"
|
||||
"used": "Used",
|
||||
"redeemCode": "Redeem Code",
|
||||
"redeemCodeTitle": "Redeem Code",
|
||||
"redeemCodeDescription": "Enter redemption code to get subscription plan",
|
||||
"enterRedemptionCode": "Please enter redemption code",
|
||||
"redeemButton": "Redeem Now",
|
||||
"redeeming": "Redeeming...",
|
||||
"redeemSuccess": "Redemption successful! Subscription has been activated.",
|
||||
"redeemFailed": "Redemption failed"
|
||||
}
|
||||
|
||||
@ -31,5 +31,13 @@
|
||||
"subscriptionUrl": "订阅链接",
|
||||
"totalTraffic": "总流量",
|
||||
"unknown": "未知",
|
||||
"used": "已使用"
|
||||
"used": "已使用",
|
||||
"redeemCode": "兑换 CDK",
|
||||
"redeemCodeTitle": "CDK 兑换",
|
||||
"redeemCodeDescription": "输入兑换码以获取订阅套餐",
|
||||
"enterRedemptionCode": "请输入兑换码",
|
||||
"redeemButton": "立即兑换",
|
||||
"redeeming": "兑换中...",
|
||||
"redeemSuccess": "兑换成功!",
|
||||
"redeemFailed": "兑换失败"
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ import Subscribe from "../../subscribe";
|
||||
import Renewal from "../../subscribe/renewal";
|
||||
import ResetTraffic from "../../subscribe/reset-traffic";
|
||||
import Unsubscribe from "../../subscribe/unsubscribe";
|
||||
import RedeemCode from "./redeem-code";
|
||||
|
||||
const platforms: (keyof API.DownloadLink)[] = [
|
||||
"windows",
|
||||
@ -164,6 +165,7 @@ export default function Content() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<RedeemCode onSuccess={refetch} />
|
||||
<div className="flex flex-wrap justify-between gap-4">
|
||||
{availablePlatforms.length > 0 && (
|
||||
<Tabs
|
||||
|
||||
85
apps/user/src/sections/user/dashboard/redeem-code.tsx
Normal file
85
apps/user/src/sections/user/dashboard/redeem-code.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Button } from "@workspace/ui/components/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@workspace/ui/components/card";
|
||||
import { Input } from "@workspace/ui/components/input";
|
||||
import { Label } from "@workspace/ui/components/label";
|
||||
import { redeemCode } from "@workspace/ui/services/user/user";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface RedeemCodeProps {
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export default function RedeemCode({ onSuccess }: RedeemCodeProps) {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const [code, setCode] = useState("");
|
||||
|
||||
const redeemMutation = useMutation({
|
||||
mutationFn: (code: string) => redeemCode({ code }),
|
||||
onSuccess: (response) => {
|
||||
toast.success(response.data.message || t("redeemSuccess", "兑换成功"));
|
||||
setCode("");
|
||||
onSuccess?.();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
const errorMessage =
|
||||
error?.response?.data?.message || t("redeemFailed", "兑换失败");
|
||||
toast.error(errorMessage);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!code.trim()) {
|
||||
toast.error(t("pleaseEnterCode", "请输入兑换码"));
|
||||
return;
|
||||
}
|
||||
redeemMutation.mutate(code.trim());
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>{t("redeemCode", "CDK 兑换")}</CardTitle>
|
||||
<CardDescription>
|
||||
{t("redeemCodeDescription", "输入兑换码即可兑换对应的订阅套餐")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="redemption-code">
|
||||
{t("redemptionCode", "兑换码")}
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="redemption-code"
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder={t(
|
||||
"enterRedemptionCode",
|
||||
"请输入兑换码"
|
||||
)}
|
||||
value={code}
|
||||
/>
|
||||
<Button
|
||||
disabled={redeemMutation.isPending || !code.trim()}
|
||||
loading={redeemMutation.isPending}
|
||||
type="submit"
|
||||
>
|
||||
{t("redeem", "兑换")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -268,6 +268,24 @@ export async function updateUserRules(
|
||||
);
|
||||
}
|
||||
|
||||
/** Redeem Code POST /v1/public/redemption/ */
|
||||
export async function redeemCode(
|
||||
body: { code: string },
|
||||
options?: { [key: string]: any }
|
||||
) {
|
||||
return request<API.Response & { data?: { message: string } }>(
|
||||
`${import.meta.env.VITE_API_PREFIX || ""}/v1/public/redemption/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** Query User Subscribe GET /v1/public/user/subscribe */
|
||||
export async function queryUserSubscribe(options?: { [key: string]: any }) {
|
||||
return request<API.Response & { data?: API.QueryUserSubscribeListResponse }>(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user