From 0b14e77f64e6daa7f087f0150288e4ff3ad5d238 Mon Sep 17 00:00:00 2001 From: kang Date: Sun, 14 Jun 2026 21:13:24 +0800 Subject: [PATCH] feat: enhance player login validation and update betting preview - Added username and password validation to the player login screen, improving user feedback for input errors. - Updated the betting preview dialog to conditionally display rebate information based on the applied instant rebate status. - Enhanced documentation in AGENTS.md to clarify credit flow and settlement processes. - Modified .env.example to provide clearer instructions for multi-site deployment configurations. --- .env.example | 2 +- AGENTS.md | 1 + src/features/hall/hall-bet-preview-dialog.tsx | 22 +++++++++++--- src/features/player/player-login-screen.tsx | 25 ++++++++++++++-- src/lib/player-input-validation.ts | 30 +++++++++++++++++++ src/types/api/ticket.ts | 1 + 6 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 src/lib/player-input-validation.ts diff --git a/.env.example b/.env.example index d1b1413..cbd6815 100644 --- a/.env.example +++ b/.env.example @@ -22,7 +22,7 @@ LOTTERY_API_UPSTREAM=http://127.0.0.1:8000 # 可选:入口授权失败时返回主站(build 前设置,会打进 JS) # NEXT_PUBLIC_MAIN_SITE_URL=http://localhost:5173 -# 可选:代理玩家登录时隐式携带的站点编号(不配则后端用 LOTTERY_DEFAULT_SITE_CODE) +# 多站点部署时可选:玩家端登录请求附带 site_code 以优先匹配该站(玩家界面无需填写) # NEXT_PUBLIC_PLAYER_SITE_CODE=default_site # Reverb:本地全栈联调时取消注释,并 php artisan reverb:start;不配则走轮询 diff --git a/AGENTS.md b/AGENTS.md index 7e2b30f..ec133f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,3 +11,4 @@ This version has breaking changes — APIs, conventions, and file structure may ## Learned Workspace Facts - 信用盘流水对外类型:`win_credit`(中奖释额)、`bill_settlement`(账期收付),与钱包 `prize`/派彩文案分离。 +- 信用流水结余:`game_settlement_win`/`settlement_confirm`/`bet_hold_release` 会减 `used_credit` 并倒推结余;`settlement_payout`(账期收付)仅记账,`affects_available_credit: false`。 diff --git a/src/features/hall/hall-bet-preview-dialog.tsx b/src/features/hall/hall-bet-preview-dialog.tsx index e20f3f5..da35ee2 100644 --- a/src/features/hall/hall-bet-preview-dialog.tsx +++ b/src/features/hall/hall-bet-preview-dialog.tsx @@ -116,6 +116,10 @@ export function HallBetPreviewDialog({ const { t } = useTranslation("player"); const summary = data?.summary; const lines = data?.lines ?? []; + const periodRebate = summary?.instant_rebate_applied === false; + const rebateLabel = periodRebate + ? t("hall.preview.periodRebate", { defaultValue: "账期回水" }) + : t("hall.preview.rebate"); useEffect(() => { if (open && !placing && !data) { @@ -216,7 +220,7 @@ export function HallBetPreviewDialog({ {t("hall.preview.amount")} - {t("hall.preview.rebate")} + {rebateLabel} {t("hall.preview.estimatedMax")} @@ -245,7 +249,8 @@ export function HallBetPreviewDialog({ {formatMinorAsCurrency(ln.total_bet_amount, currencyCode)} - -{formatMinorAsCurrency(ln.rebate_amount, currencyCode).replace(`${currencyCode} `, "")} + {periodRebate ? "" : "-"} + {formatMinorAsCurrency(ln.rebate_amount, currencyCode).replace(`${currencyCode} `, "")} {formatMinorAsCurrency(ln.estimated_max_payout, currencyCode)} @@ -268,9 +273,9 @@ export function HallBetPreviewDialog({

-

{t("hall.preview.rebate")}

+

{rebateLabel}

- - + {periodRebate ? "" : "-"} {formatMinorAsCurrency(summary.total_rebate_amount, currencyCode).replace( `${currencyCode} `, "", @@ -292,6 +297,15 @@ export function HallBetPreviewDialog({

) : null} + {periodRebate ? ( +

+ {t("hall.preview.periodRebateHint", { + defaultValue: + "信用盘回水在账期结算入账,下注时按全额占用可用信用,此处为账期回水预估。", + })} +

+ ) : null} + {data.warnings.length > 0 ? ( ) : ( diff --git a/src/features/player/player-login-screen.tsx b/src/features/player/player-login-screen.tsx index 380c361..e8075ad 100644 --- a/src/features/player/player-login-screen.tsx +++ b/src/features/player/player-login-screen.tsx @@ -15,10 +15,12 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { usePlayerSessionStore } from "@/stores/player-session-store"; +import { + validatePlayerLoginPassword, + validatePlayerLoginUsername, +} from "@/lib/player-input-validation"; import { LotteryApiBizError } from "@/types/api/errors"; -const DEPLOY_SITE_CODE = process.env.NEXT_PUBLIC_PLAYER_SITE_CODE?.trim() ?? ""; - function stripSearchParamFromBrowserUrl(name: string): void { if (typeof window === "undefined") return; const url = new URL(window.location.href); @@ -58,10 +60,27 @@ export function PlayerLoginScreen(): React.ReactElement { return; } + const usernameIssue = validatePlayerLoginUsername(username); + if (usernameIssue === "invalid_charset") { + toast.error( + t("login.usernameInvalidCharset", { + defaultValue: "账号只能使用字母、数字、点(.)、下划线和连字符", + }), + ); + return; + } + + const passwordIssue = validatePlayerLoginPassword(password); + if (passwordIssue === "too_short") { + toast.error( + t("login.passwordMinLength", { defaultValue: "密码至少需要 6 个字符" }), + ); + return; + } + setLoading(true); try { const data = await postPlayerAuthLogin({ - ...(DEPLOY_SITE_CODE !== "" ? { site_code: DEPLOY_SITE_CODE } : {}), username: username.trim(), password, }); diff --git a/src/lib/player-input-validation.ts b/src/lib/player-input-validation.ts new file mode 100644 index 0000000..8c5798f --- /dev/null +++ b/src/lib/player-input-validation.ts @@ -0,0 +1,30 @@ +/** 彩票端登录账号:字母、数字、点、下划线、连字符。 */ +export const PLAYER_ACCOUNT_PATTERN = /^[a-zA-Z0-9._-]+$/; + +export const PLAYER_PASSWORD_MIN_LENGTH = 6; + +export type AccountValidationIssue = "empty" | "invalid_charset"; +export type PasswordValidationIssue = "empty" | "too_short"; + +export function validatePlayerLoginUsername(value: string): AccountValidationIssue | null { + const trimmed = value.trim(); + if (trimmed === "") { + return "empty"; + } + if (!PLAYER_ACCOUNT_PATTERN.test(trimmed)) { + return "invalid_charset"; + } + + return null; +} + +export function validatePlayerLoginPassword(value: string): PasswordValidationIssue | null { + if (value === "") { + return "empty"; + } + if (value.length < PLAYER_PASSWORD_MIN_LENGTH) { + return "too_short"; + } + + return null; +} diff --git a/src/types/api/ticket.ts b/src/types/api/ticket.ts index eff822f..bf0f280 100644 --- a/src/types/api/ticket.ts +++ b/src/types/api/ticket.ts @@ -53,6 +53,7 @@ export type TicketPreviewData = { total_rebate_amount: number; total_actual_deduct: number; total_estimated_payout: number; + instant_rebate_applied?: boolean; }; lines: TicketPreviewLine[]; warnings: TicketPreviewWarning[];