diff --git a/apps/admin/dist.zip b/apps/admin/dist.zip
index 4d195bb..2075c25 100644
Binary files a/apps/admin/dist.zip and b/apps/admin/dist.zip differ
diff --git a/apps/admin/public/assets/locales/en-US/lottery.json b/apps/admin/public/assets/locales/en-US/lottery.json
index 878c459..2b24e71 100644
--- a/apps/admin/public/assets/locales/en-US/lottery.json
+++ b/apps/admin/public/assets/locales/en-US/lottery.json
@@ -92,7 +92,7 @@
"iconUrlInvalid": "Must be a valid URL",
"isFallback": "Fallback Prize",
"isFallbackHint": "Awarded when limited prizes are sold out",
- "manualPrizeHint": "Manual prize: the winner submits claim info, then an operator approves and marks it paid/shipped under the Claims tab.",
+ "manualPrizeHint": "Manual prize: no extra config. When won, the user contacts customer service with a screenshot to redeem — no in-app claim form.",
"name": "Prize Name",
"namePlaceholder": "e.g. 1-Day Pass",
"nameRequired": "Name is required",
diff --git a/apps/admin/public/assets/locales/zh-CN/lottery.json b/apps/admin/public/assets/locales/zh-CN/lottery.json
index c092b97..e6de359 100644
--- a/apps/admin/public/assets/locales/zh-CN/lottery.json
+++ b/apps/admin/public/assets/locales/zh-CN/lottery.json
@@ -92,7 +92,7 @@
"iconUrlInvalid": "请输入有效的 URL",
"isFallback": "保底奖品",
"isFallbackHint": "限量奖品抽完后发放该奖品",
- "manualPrizeHint": "人工奖:用户先提交领奖信息,运营在「领奖工单」页审核并标记发放/发货。",
+ "manualPrizeHint": "人工发放奖品:无需额外配置。中奖后用户凭截图联系人工客服兑换,无需在 app 内填领奖表单。",
"name": "奖品名称",
"namePlaceholder": "例如:1 天时长卡",
"nameRequired": "请输入奖品名称",
diff --git a/apps/admin/src/sections/lottery/prize-form.tsx b/apps/admin/src/sections/lottery/prize-form.tsx
index 3866492..26dd1de 100644
--- a/apps/admin/src/sections/lottery/prize-form.tsx
+++ b/apps/admin/src/sections/lottery/prize-form.tsx
@@ -42,7 +42,6 @@ import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { z } from "zod";
-const CENTS_PER_YUAN = 100;
// 概率单位约定:weight 1000 = 1%(支持 3 位小数概率,总权重 100000 = 100%)。
const WEIGHT_PER_PERCENT = 1000;
@@ -101,13 +100,6 @@ const getPrizeSchema = (t: TranslateFn) =>
is_fallback: z.boolean(),
duration_days: z.number().optional(),
subscribe_id: z.number().optional(),
- amount_yuan: z.number().optional(),
- // 人工奖(crypto / physical / manual_other)通用/专属字段
- crypto_amount: z.string().optional(),
- crypto_currency: z.string().optional(),
- crypto_networks: z.string().optional(), // 逗号分隔,提交时 split 成 string[]
- sku_name: z.string().optional(),
- claim_ttl_hours: z.number().optional(),
})
.superRefine((data, ctx) => {
const issue = (path: string, message: string) => {
@@ -124,45 +116,6 @@ const getPrizeSchema = (t: TranslateFn) =>
)
);
}
- if (data.type === "crypto") {
- if (!data.crypto_amount?.trim()) {
- issue(
- "crypto_amount",
- t("prize.cryptoAmountRequired", "Amount is required")
- );
- }
- if (!data.crypto_currency?.trim()) {
- issue(
- "crypto_currency",
- t("prize.cryptoCurrencyRequired", "Currency is required")
- );
- }
- const networks = (data.crypto_networks ?? "")
- .split(",")
- .map((n) => n.trim())
- .filter(Boolean);
- if (networks.length === 0) {
- issue(
- "crypto_networks",
- t(
- "prize.cryptoNetworksRequired",
- "At least one network is required (comma-separated)"
- )
- );
- }
- }
- if (
- data.claim_ttl_hours !== undefined &&
- (!Number.isInteger(data.claim_ttl_hours) || data.claim_ttl_hours <= 0)
- ) {
- issue(
- "claim_ttl_hours",
- t(
- "prize.claimTtlInvalid",
- "Claim window (hours) must be a positive integer"
- )
- );
- }
if (!(data.unlimited_stock || isPositiveInt(data.total_stock))) {
issue(
"total_stock",
@@ -187,9 +140,6 @@ interface PrizeFormProps {
function toFormValues(prize?: API.LotteryPrize): PrizeFormValues {
const config = prize?.config ?? {};
- const networks = Array.isArray(config.networks)
- ? (config.networks as string[]).join(", ")
- : undefined;
return {
type: (prize?.type as PrizeFormValues["type"]) ?? "vpn_duration",
name: prize?.name ?? "",
@@ -200,26 +150,10 @@ function toFormValues(prize?: API.LotteryPrize): PrizeFormValues {
is_fallback: prize?.is_fallback ?? false,
duration_days: config.duration_days as number | undefined,
subscribe_id: config.subscribe_id as number | undefined,
- amount_yuan:
- typeof config.amount_cents === "number"
- ? config.amount_cents / CENTS_PER_YUAN
- : undefined,
- crypto_amount: config.amount as string | undefined,
- crypto_currency: config.currency as string | undefined,
- crypto_networks: networks,
- sku_name: config.sku_name as string | undefined,
- claim_ttl_hours: config.claim_ttl_hours as number | undefined,
};
}
function toConfig(values: PrizeFormValues): Record
+ {t( + "prize.manualPrizeHint", + "Manual prize: no extra config. When won, the user contacts customer service with a screenshot to redeem — no in-app claim form." )} - {renderInput( - "crypto_currency", - t("prize.cryptoCurrency", "Currency"), - { placeholder: "USDT / BTC / ETH" } - )} - {renderInput( - "crypto_networks", - t("prize.cryptoNetworks", "Networks"), - { placeholder: "TRC20, ERC20, BTC" }, - t( - "prize.cryptoNetworksHint", - "Comma-separated. The user picks one of these when claiming." - ) - )} - > - )} - {type === "crypto" && ( - <> - {renderInput( - "claim_ttl_hours", - t("prize.claimTtlHours", "Claim window (hours)"), - { - type: "text", - inputMode: "numeric", - formatOutput: numberOutput, - placeholder: "168", - suffix: t("prize.hoursSuffix", "hours"), - }, - t( - "prize.claimTtlHint", - "How long the user has to submit claim info. Default 168h (7 days)." - ) - )} -
- {t( - "prize.manualPrizeHint", - "Manual prize: the winner submits claim info, then an operator approves and marks it paid/shipped under the Claims tab." - )} -
- > + )} {renderInput( "weight",