feat: improve redemption feature i18n and error code handling

- Complete internationalization for redemption records dialog
  - Add i18n for table headers and unit time translations
  - Optimize redemption form validation error messages with i18n
  - Add 50003 error code handling for coupon plan incompatibility
  - Fix TypeScript errors in redemption-related components
  - Fix TypeScript errors in user announcement page
This commit is contained in:
EUForest 2026-01-07 15:38:42 +08:00
parent 93926d9c99
commit 585874777f
8 changed files with 46 additions and 22 deletions

View File

@ -26,14 +26,19 @@
"halfYear": "Half Year", "halfYear": "Half Year",
"month": "Month", "month": "Month",
"quarter": "Quarter", "quarter": "Quarter",
"quantityRequired": "Quantity is required",
"selectPlan": "Select Redemption Plan", "selectPlan": "Select Redemption Plan",
"selectUnitTime": "Select Redemption Duration Unit", "selectUnitTime": "Select Redemption Duration Unit",
"subscribePlan": "Redemption Plan", "subscribePlan": "Redemption Plan",
"subscribePlanRequired": "Subscribe plan is required",
"totalCount": "Available Uses", "totalCount": "Available Uses",
"totalCountPlaceholder": "Available Uses", "totalCountPlaceholder": "Available Uses",
"totalCountRequired": "Total count is required",
"unitTime": "Redemption Duration Unit", "unitTime": "Redemption Duration Unit",
"unitTimeRequired": "Unit time is required",
"year": "Year" "year": "Year"
}, },
"id": "ID",
"loading": "Loading...", "loading": "Loading...",
"next": "Next", "next": "Next",
"noRecords": "No records found", "noRecords": "No records found",

View File

@ -26,14 +26,19 @@
"halfYear": "半年", "halfYear": "半年",
"month": "月", "month": "月",
"quarter": "季度", "quarter": "季度",
"quantityRequired": "数量为必填项",
"selectPlan": "选择兑换套餐", "selectPlan": "选择兑换套餐",
"selectUnitTime": "选择兑换时长单位", "selectUnitTime": "选择兑换时长单位",
"subscribePlan": "兑换套餐", "subscribePlan": "兑换套餐",
"subscribePlanRequired": "兑换套餐为必填项",
"totalCount": "兑换码可用次数", "totalCount": "兑换码可用次数",
"totalCountPlaceholder": "兑换码可用次数", "totalCountPlaceholder": "兑换码可用次数",
"totalCountRequired": "兑换码可用次数为必填项",
"unitTime": "兑换时长单位", "unitTime": "兑换时长单位",
"unitTimeRequired": "兑换时长单位为必填项",
"year": "年" "year": "年"
}, },
"id": "ID",
"loading": "加载中...", "loading": "加载中...",
"next": "下一页", "next": "下一页",
"noRecords": "暂无兑换记录", "noRecords": "暂无兑换记录",

View File

@ -1,4 +1,3 @@
import { Badge } from "@workspace/ui/components/badge";
import { Button } from "@workspace/ui/components/button"; import { Button } from "@workspace/ui/components/button";
import { Switch } from "@workspace/ui/components/switch"; import { Switch } from "@workspace/ui/components/switch";
import { ConfirmButton } from "@workspace/ui/composed/confirm-button"; import { ConfirmButton } from "@workspace/ui/composed/confirm-button";

View File

@ -26,14 +26,14 @@ import { useTranslation } from "react-i18next";
import { z } from "zod"; import { z } from "zod";
import { useSubscribe } from "@/stores/subscribe"; import { useSubscribe } from "@/stores/subscribe";
const formSchema = z.object({ const getFormSchema = (t: (key: string, defaultValue: string) => string) => z.object({
id: z.number().optional(), id: z.number().optional(),
code: z.string().optional(), code: z.string().optional(),
batch_count: z.number().optional(), batch_count: z.number().optional(),
total_count: z.number().min(1, "Total count is required"), total_count: z.number().min(1, t("form.totalCountRequired", "Total count is required")),
subscribe_plan: z.number().min(1, "Subscribe plan is required"), subscribe_plan: z.number().min(1, t("form.subscribePlanRequired", "Subscribe plan is required")),
unit_time: z.string().min(1, "Unit time is required"), unit_time: z.string().min(1, t("form.unitTimeRequired", "Unit time is required")),
quantity: z.number().min(1, "Quantity is required"), quantity: z.number().min(1, t("form.quantityRequired", "Quantity is required")),
}); });
interface RedemptionFormProps<T> { interface RedemptionFormProps<T> {
@ -52,6 +52,7 @@ export default function RedemptionForm<T extends Record<string, any>>({
title, title,
}: RedemptionFormProps<T>) { }: RedemptionFormProps<T>) {
const { t } = useTranslation("redemption"); const { t } = useTranslation("redemption");
const formSchema = getFormSchema(t);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const form = useForm({ const form = useForm({

View File

@ -1,4 +1,3 @@
import { Badge } from "@workspace/ui/components/badge";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -81,7 +80,7 @@ export default function RedemptionRecords({
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>ID</TableHead> <TableHead>{t("id", "ID")}</TableHead>
<TableHead>{t("userId", "User ID")}</TableHead> <TableHead>{t("userId", "User ID")}</TableHead>
<TableHead>{t("subscribeId", "Subscribe ID")}</TableHead> <TableHead>{t("subscribeId", "Subscribe ID")}</TableHead>
<TableHead>{t("unitTime", "Unit Time")}</TableHead> <TableHead>{t("unitTime", "Unit Time")}</TableHead>
@ -90,20 +89,29 @@ export default function RedemptionRecords({
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{records.map((record) => ( {records.map((record) => {
<TableRow key={record.id}> const unitTimeMap: Record<string, string> = {
<TableCell>{record.id}</TableCell> day: t("form.day", "Day"),
<TableCell>{record.user_id}</TableCell> month: t("form.month", "Month"),
<TableCell>{record.subscribe_id}</TableCell> quarter: t("form.quarter", "Quarter"),
<TableCell>{record.unit_time}</TableCell> half_year: t("form.halfYear", "Half Year"),
<TableCell>{record.quantity}</TableCell> year: t("form.year", "Year"),
<TableCell> };
{record.redeemed_at return (
? formatDate(record.redeemed_at) <TableRow key={record.id}>
: "--"} <TableCell>{record.id}</TableCell>
</TableCell> <TableCell>{record.user_id}</TableCell>
</TableRow> <TableCell>{record.subscribe_id}</TableCell>
))} <TableCell>{unitTimeMap[record.unit_time] || record.unit_time}</TableCell>
<TableCell>{record.quantity}</TableCell>
<TableCell>
{record.redeemed_at
? formatDate(record.redeemed_at)
: "--"}
</TableCell>
</TableRow>
);
})}
</TableBody> </TableBody>
</Table> </Table>
{total > pagination.size && ( {total > pagination.size && (

View File

@ -40,6 +40,7 @@
"40005": "You do not have access permission, please contact the administrator if you have any questions.", "40005": "You do not have access permission, please contact the administrator if you have any questions.",
"50001": "Corresponding coupon information not found, please check and try again.", "50001": "Corresponding coupon information not found, please check and try again.",
"50002": "The coupon has been used, cannot be used again.", "50002": "The coupon has been used, cannot be used again.",
"50003": "This coupon code is not supported by the current purchase plan.",
"60001": "Subscription has expired, please renew before using.", "60001": "Subscription has expired, please renew before using.",
"60002": "Unable to use the subscription at the moment, please try again later.", "60002": "Unable to use the subscription at the moment, please try again later.",
"60003": "An existing subscription is detected. Please cancel it before proceeding.", "60003": "An existing subscription is detected. Please cancel it before proceeding.",

View File

@ -40,6 +40,7 @@
"40005": "您没有访问权限,如有疑问请联系管理员。", "40005": "您没有访问权限,如有疑问请联系管理员。",
"50001": "找不到对应的优惠券信息,请检查后重试。", "50001": "找不到对应的优惠券信息,请检查后重试。",
"50002": "该优惠券已被使用,无法再次使用。", "50002": "该优惠券已被使用,无法再次使用。",
"50003": "该优惠码不被当前购买套餐支持。",
"60001": "订阅已过期,请续费后使用。", "60001": "订阅已过期,请续费后使用。",
"60002": "暂时无法使用该订阅,请稍后再试。", "60002": "暂时无法使用该订阅,请稍后再试。",
"60003": "检测到现有订阅,请先取消后再继续。", "60003": "检测到现有订阅,请先取消后再继续。",

View File

@ -119,6 +119,10 @@ function handleError(response: {
"components:error.50002", "components:error.50002",
"The coupon has been used, cannot be used again." "The coupon has been used, cannot be used again."
), ),
50003: t(
"components:error.50003",
"This coupon code is not supported by the current purchase plan."
),
60001: t( 60001: t(
"components:error.60001", "components:error.60001",
"Subscription has expired, please renew before using." "Subscription has expired, please renew before using."