diff --git a/public/entry/image4.png b/public/entry/image4.png new file mode 100644 index 0000000..aa01a60 Binary files /dev/null and b/public/entry/image4.png differ diff --git a/public/entry/image5.png b/public/entry/image5.png new file mode 100644 index 0000000..b4083d0 Binary files /dev/null and b/public/entry/image5.png differ diff --git a/public/entry/image6.png b/public/entry/image6.png new file mode 100644 index 0000000..b9d46a5 Binary files /dev/null and b/public/entry/image6.png differ diff --git a/src/app/(player)/(main)/results/check/page.tsx b/src/app/(player)/(main)/results/check/page.tsx new file mode 100644 index 0000000..31e0afe --- /dev/null +++ b/src/app/(player)/(main)/results/check/page.tsx @@ -0,0 +1,5 @@ +import { CheckWinningScreen } from "@/features/results/check-winning-screen"; + +export default function CheckWinningPage() { + return ; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 13a1763..4c55015 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import { Providers } from "@/components/providers"; -import { DEFAULT_LANGUAGE } from "@/i18n"; +import { DEFAULT_LANGUAGE } from "@/i18n/language"; import "./globals.css"; const geistSans = Geist({ diff --git a/src/components/providers.tsx b/src/components/providers.tsx index 61ac75f..38c9cc3 100644 --- a/src/components/providers.tsx +++ b/src/components/providers.tsx @@ -2,8 +2,6 @@ import type { ReactNode } from "react"; -import { ThemeProvider } from "next-themes"; - import { Toaster } from "@/components/ui/sonner"; import { ErrorProvider } from "@/components/error-provider"; import { IframeBridge } from "@/components/iframe-bridge"; @@ -16,7 +14,7 @@ type ProvidersProps = { export function Providers({ children }: ProvidersProps): ReactNode { return ( - + <> {/* iframe 通信桥接 - 支持主站嵌入 */} @@ -26,6 +24,6 @@ export function Providers({ children }: ProvidersProps): ReactNode { - + ); } diff --git a/src/features/hall/hall-bet-preview-dialog.tsx b/src/features/hall/hall-bet-preview-dialog.tsx index 7eb7ec4..8739221 100644 --- a/src/features/hall/hall-bet-preview-dialog.tsx +++ b/src/features/hall/hall-bet-preview-dialog.tsx @@ -7,6 +7,8 @@ import { WalletCards, XIcon, } from "lucide-react"; +import Image from "next/image"; +import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; @@ -65,11 +67,17 @@ function SubmittingPanel() { showCloseButton={false} className="max-w-[340px] overflow-hidden rounded-2xl border border-white/70 bg-white p-0 shadow-[0_24px_70px_rgba(15,23,42,0.22)]" > -
-
- N -
-
+
+ +
@@ -105,6 +113,12 @@ export function HallBetPreviewDialog({ const summary = data?.summary; const lines = data?.lines ?? []; + useEffect(() => { + if (open && !placing && !data) { + onOpenChange(false); + } + }, [data, onOpenChange, open, placing]); + if (placing) { return ( {}}> @@ -113,13 +127,17 @@ export function HallBetPreviewDialog({ ); } + if (!data) { + return null; + } + return ( -
+
{!data ? ( @@ -274,7 +291,7 @@ export function HallBetPreviewDialog({
-
+
@@ -57,8 +68,7 @@ export function HallBetResultDialog({
{!data ? ( @@ -190,7 +200,7 @@ export function HallBetResultDialog({
-
+
+
+ + +
+
+

+ {t("results.check.recent", { defaultValue: "最近查询" })} +

+ {recent.length > 0 ? ( + + ) : null} +
+
+ {recent.length === 0 ? ( +

+ {t("results.check.noRecent", { defaultValue: "暂无查询记录。" })} +

+ ) : ( + recent.map((row) => ( + + )) + )} +
+
+
+ + { + if (!open) setResult(null); + }} + onCheckAnother={() => { + setResult(null); + setTicketNo(""); + }} + /> + + ); +} + +function WinningResultDialog({ + open, + data, + query, + onOpenChange, + onCheckAnother, +}: { + open: boolean; + data: WinningCheckResult | null; + query: string; + onOpenChange: (open: boolean) => void; + onCheckAnother: () => void; +}) { + const { t } = useTranslation("player"); + const totalWin = (data?.match.total_win_minor ?? 0) + (data?.match.total_jackpot_win_minor ?? 0); + const isWon = totalWin > 0 || (data?.match.winning_ticket_count ?? 0) > 0; + const firstTicket = useMemo(() => data?.tickets[0] ?? null, [data]); + + if (!data) return null; + + return ( + + + +
+ +
+ +
+ + {isWon + ? t("results.check.winTitle", { defaultValue: "恭喜,你中奖了" }) + : t("results.check.noWinTitle", { defaultValue: "未查询到中奖" })} + + + {t("results.check.ticketNumber", { defaultValue: "票号 / 号码" })} + +
+ +
+ {query} +
+ +
+
+

+ {t("results.check.match", { defaultValue: "匹配" })} +

+

+ {firstTicket ? playLabel(firstTicket.play_code, t) : isWon ? t("orders.hit", { defaultValue: "命中" }) : "—"} +

+
+
+

+ {t("results.check.amount", { defaultValue: "中奖金额" })} +

+

+ {formatMinorAsCurrency(totalWin, firstTicket?.currency_code ?? "NPR")} +

+
+
+ +
+

+ {t("results.check.drawInfo", { defaultValue: "开奖信息" })} +

+
+
+

{t("results.check.issueNo", { defaultValue: "期号" })}

+

{data.draw.draw_no}

+
+
+

{t("results.businessDate")}

+

{data.draw.business_date}

+
+
+

{t("results.drawTime", { time: "" }).replace(":", "").trim()}

+

+ {formatLotteryInstant(data.draw.draw_time_iso ?? data.draw.draw_time ?? null)} +

+
+
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/src/features/results/draw-result-detail-screen.tsx b/src/features/results/draw-result-detail-screen.tsx index 8de326f..c8f0e74 100644 --- a/src/features/results/draw-result-detail-screen.tsx +++ b/src/features/results/draw-result-detail-screen.tsx @@ -271,7 +271,7 @@ export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps) {t("results.hitHint")}

{t("results.viewMyWinning")} diff --git a/src/features/results/twenty-three-results-grid.tsx b/src/features/results/twenty-three-results-grid.tsx index 2bda543..9af54d2 100644 --- a/src/features/results/twenty-three-results-grid.tsx +++ b/src/features/results/twenty-three-results-grid.tsx @@ -1,4 +1,5 @@ import type { DrawResultsNumbers } from "@/types/api/draw-results"; +import { Trophy } from "lucide-react"; import { useTranslation } from "react-i18next"; import { norm4d } from "@/lib/norm-4d"; @@ -22,65 +23,105 @@ export function TwentyThreeResultsGrid({ const consos = numbers.consolation ?? []; const hits = highlighted4d ?? null; - const cellBase = - "flex min-h-[2.75rem] items-center justify-center rounded-md border font-mono text-base font-semibold tracking-wide tabular-nums"; - - const cellTone = (raw: string) => { + const isHit = (raw: string): boolean => { const v = (raw || "").trim(); - const isHit = + return ( hits !== null && hits.size > 0 && v !== "" && v !== "—" && - hits.has(norm4d(v)); - return cn( - cellBase, - isHit - ? "border-amber-500/80 bg-gradient-to-br from-amber-300 via-amber-400 to-amber-500 text-amber-950 shadow-[inset_0_1px_0_rgba(255,255,255,0.35)]" - : "border-border bg-card", + hits.has(norm4d(v)) ); }; + const smallCellTone = (raw: string, tone: "red" | "blue") => + cn( + "grid min-h-[3.875rem] grid-rows-[auto_1fr] rounded-lg border bg-white px-1.5 py-2 text-center shadow-[0_6px_16px_rgba(15,23,42,0.04)]", + tone === "red" ? "border-red-100 text-[#e5002c]" : "border-blue-100 text-[#0b56b7]", + isHit(raw) && "border-amber-400 bg-amber-50 text-amber-700 shadow-[0_8px_18px_rgba(245,158,11,0.16)]", + ); + + const prizeCards = [ + { + key: "1st" as const, + label: t("results.grid.first"), + value: numbers["1st"] || "—", + tone: "red", + border: "border-[#ffb8c3]", + text: "text-[#e5002c]", + wash: "from-[#fff4f6] to-white", + }, + { + key: "2nd" as const, + label: t("results.grid.second"), + value: numbers["2nd"] || "—", + tone: "blue", + border: "border-[#b9ccf6]", + text: "text-[#0b56b7]", + wash: "from-[#f3f7ff] to-white", + }, + { + key: "3rd" as const, + label: t("results.grid.third"), + value: numbers["3rd"] || "—", + tone: "green", + border: "border-[#bde7cc]", + text: "text-[#0a8f3e]", + wash: "from-[#f0fff5] to-white", + }, + ]; + return (
- {(["1st", "2nd", "3rd"] as const).map((key) => ( -
- - {key === "1st" - ? t("results.grid.first") - : key === "2nd" - ? t("results.grid.second") - : t("results.grid.third")} - -
- {numbers[key] || "—"} + {prizeCards.map((card) => ( +
+
+
+

{card.label}

+

{card.value}

))}
-
-

- {t("results.grid.starter")} (Starter) +

+

+ + {t("results.grid.starter")}

-
+
{Array.from({ length: 10 }).map((_, i) => ( -
- {starters[i] ?? "—"} +
+ {i + 1} + + {starters[i] ?? "—"} +
))}
-
-

- {t("results.grid.consolation")} (Consolation) +

+

+ + {t("results.grid.consolation")}

-
+
{Array.from({ length: 10 }).map((_, i) => ( -
- {consos[i] ?? "—"} +
+ {i + 1} + + {consos[i] ?? "—"} +
))}
diff --git a/src/features/wallet/wallet-screen.tsx b/src/features/wallet/wallet-screen.tsx index d3832c9..90f126f 100644 --- a/src/features/wallet/wallet-screen.tsx +++ b/src/features/wallet/wallet-screen.tsx @@ -1,6 +1,7 @@ "use client"; import { Wallet } from "lucide-react"; +import Image from "next/image"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -157,6 +158,13 @@ export function WalletScreen() { ) : null}
+
diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 3db6bf7..7864a0e 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -16,17 +16,17 @@ import zhCommon from "./locales/zh/common.json"; import zhEntry from "./locales/zh/entry.json"; import zhLayout from "./locales/zh/layout.json"; import zhPlayer from "./locales/zh/player.json"; - -/** 对齐后端与产品:尼泊尔语 / 英语 / 中文(简体) */ -export const SUPPORTED_LANGUAGES = [ - { code: "en" as const, flag: "🇺🇸" }, - { code: "ne" as const, flag: "🇳🇵" }, - { code: "zh" as const, flag: "🇨🇳" }, -]; - -export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number]["code"]; - -export const DEFAULT_LANGUAGE: AppLanguage = "en"; +import { + DEFAULT_LANGUAGE, + normalizeLanguage, + type AppLanguage, +} from "@/i18n/language"; +export { + DEFAULT_LANGUAGE, + normalizeLanguage, + SUPPORTED_LANGUAGES, + type AppLanguage, +} from "@/i18n/language"; const namespaces = ["common", "entry", "layout", "player"] as const; @@ -54,13 +54,6 @@ const resources = { Record<(typeof namespaces)[number], Record> >; -export function normalizeLanguage(lang: string | undefined): AppLanguage { - const base = lang?.split("-")[0]?.toLowerCase(); - if (base === "ne") return "ne"; - if (base === "zh") return "zh"; - return "en"; -} - export function syncDocumentLanguage(lang: AppLanguage): void { if (typeof document === "undefined") return; diff --git a/src/i18n/language.ts b/src/i18n/language.ts new file mode 100644 index 0000000..09bfdca --- /dev/null +++ b/src/i18n/language.ts @@ -0,0 +1,17 @@ +/** 对齐后端与产品:尼泊尔语 / 英语 / 中文(简体) */ +export const SUPPORTED_LANGUAGES = [ + { code: "en" as const, flag: "🇺🇸" }, + { code: "ne" as const, flag: "🇳🇵" }, + { code: "zh" as const, flag: "🇨🇳" }, +]; + +export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number]["code"]; + +export const DEFAULT_LANGUAGE: AppLanguage = "zh"; + +export function normalizeLanguage(lang: string | undefined): AppLanguage { + const base = lang?.split("-")[0]?.toLowerCase(); + if (base === "ne") return "ne"; + if (base === "zh") return "zh"; + return "en"; +}