feat: integrate public settings into Play Rules screen and update button properties
- Added functionality to fetch and display dynamic rules content in the Play Rules screen. - Updated button components in HallBetResultDialog and WinningResultDialog to include nativeButton property for improved behavior.
This commit is contained in:
@@ -22,3 +22,4 @@ export {
|
||||
type WalletTransferBody,
|
||||
type WalletTransferResultData,
|
||||
} from "@/api/wallet";
|
||||
export { getPublicSettings, type SettingItem, type SettingListResponse } from "@/api/settings";
|
||||
|
||||
19
src/api/settings.ts
Normal file
19
src/api/settings.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { lotteryRequest } from "@/lib/lottery-http";
|
||||
import { API_V1_PREFIX } from "@/api/paths";
|
||||
|
||||
export type SettingItem = {
|
||||
key: string;
|
||||
value: unknown;
|
||||
group: string;
|
||||
description: string | null;
|
||||
};
|
||||
|
||||
export type SettingListResponse = {
|
||||
items: SettingItem[];
|
||||
};
|
||||
|
||||
export async function getPublicSettings(group: string): Promise<SettingListResponse> {
|
||||
return lotteryRequest.get<SettingListResponse>(`${API_V1_PREFIX}/settings`, {
|
||||
params: { group },
|
||||
});
|
||||
}
|
||||
@@ -210,6 +210,7 @@ export function HallBetResultDialog({
|
||||
<div className="grid shrink-0 grid-cols-2 gap-2.5 border-t border-[#e8eef7] bg-white p-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))] sm:p-4">
|
||||
<Button
|
||||
type="button"
|
||||
nativeButton={false}
|
||||
variant="outline"
|
||||
className="h-12 rounded-lg border-[#8dadf0] bg-white text-base font-black text-[#0b3f96] hover:bg-[#f4f8ff]"
|
||||
render={<Link href="/orders" />}
|
||||
|
||||
@@ -286,6 +286,7 @@ function WinningResultDialog({
|
||||
<div className="mt-5 grid gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
nativeButton={false}
|
||||
className="h-12 rounded-xl bg-[#07459f] text-base font-black text-white hover:bg-[#063b88]"
|
||||
render={<Link href={`/orders?draw_no=${encodeURIComponent(data.draw.draw_no)}&number=${encodeURIComponent(query)}`} />}
|
||||
>
|
||||
|
||||
@@ -1,82 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { BookOpen, ShieldCheck, TimerReset } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { getPublicSettings } from "@/api";
|
||||
import { PlayerPanel } from "@/components/layout/player-panel";
|
||||
|
||||
const prizeRows = [
|
||||
{ label: "rules.prizes.first", count: "1" },
|
||||
{ label: "rules.prizes.second", count: "1" },
|
||||
{ label: "rules.prizes.third", count: "1" },
|
||||
{ label: "rules.prizes.starter", count: "10" },
|
||||
{ label: "rules.prizes.consolation", count: "10" },
|
||||
] as const;
|
||||
|
||||
const playSections = [
|
||||
{
|
||||
title: "rules.sections.dimensions.title",
|
||||
items: [
|
||||
"rules.sections.dimensions.d4",
|
||||
"rules.sections.dimensions.d3",
|
||||
"rules.sections.dimensions.d2",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.bigSmall.title",
|
||||
items: [
|
||||
"rules.sections.bigSmall.big",
|
||||
"rules.sections.bigSmall.small",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.positions.title",
|
||||
items: [
|
||||
"rules.sections.positions.d4",
|
||||
"rules.sections.positions.d3",
|
||||
"rules.sections.positions.d2",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.box.title",
|
||||
items: [
|
||||
"rules.sections.box.straight",
|
||||
"rules.sections.box.box",
|
||||
"rules.sections.box.ibox",
|
||||
"rules.sections.box.mbox",
|
||||
"rules.sections.box.roll",
|
||||
"rules.sections.box.half",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.attributes.title",
|
||||
items: [
|
||||
"rules.sections.attributes.headTail",
|
||||
"rules.sections.attributes.oddEven",
|
||||
"rules.sections.attributes.digitSize",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.wallet.title",
|
||||
items: [
|
||||
"rules.sections.wallet.rebate",
|
||||
"rules.sections.wallet.jackpot",
|
||||
"rules.sections.wallet.close",
|
||||
"rules.sections.wallet.soldOut",
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function PlayRulesScreen() {
|
||||
const { t } = useTranslation("player");
|
||||
const [htmlContent, setHtmlContent] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadRules() {
|
||||
try {
|
||||
const res = await getPublicSettings("frontend");
|
||||
const ruleItem = res.items.find((item) => item.key === "frontend.play_rules_html");
|
||||
if (ruleItem && typeof ruleItem.value === "string" && ruleItem.value.trim() !== "") {
|
||||
setHtmlContent(ruleItem.value);
|
||||
} else {
|
||||
setHtmlContent(`<div style="text-align:center;padding:2rem;color:#64748b;">${t("rules.empty", { defaultValue: "暂无玩法规则说明" })}</div>`);
|
||||
}
|
||||
} catch {
|
||||
setHtmlContent(`<div style="text-align:center;padding:2rem;color:#64748b;">${t("rules.loadFailed", { defaultValue: "规则加载失败" })}</div>`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
void loadRules();
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<PlayerPanel
|
||||
@@ -85,103 +39,18 @@ export function PlayRulesScreen() {
|
||||
className="bg-[#f7f9fd]"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<Card className="border-[#dbe7fb] bg-white shadow-sm">
|
||||
<CardHeader className="space-y-3 pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex size-9 items-center justify-center rounded-2xl bg-[#0b3f96] text-white">
|
||||
<BookOpen className="size-4" aria-hidden />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<CardTitle className="text-base text-[#0b3f96]">
|
||||
{t("rules.quick.title")}
|
||||
</CardTitle>
|
||||
<p className="mt-0.5 text-xs text-slate-500">
|
||||
{t("rules.quick.description")}
|
||||
</p>
|
||||
<Card className="border-[#dbe7fb] bg-white shadow-sm overflow-hidden min-h-[300px]">
|
||||
<CardContent className="p-4 sm:p-6">
|
||||
{loading ? (
|
||||
<div className="flex h-[200px] items-center justify-center">
|
||||
<Loader2 className="size-6 animate-spin text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 text-center">
|
||||
<div className="rounded-2xl bg-[#eef5ff] p-3">
|
||||
<p className="text-lg font-black text-[#0b3f96]">23</p>
|
||||
<p className="text-[11px] font-semibold text-slate-500">
|
||||
{t("rules.quick.totalPrizes")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-[#fff2f4] p-3">
|
||||
<p className="text-lg font-black text-[#f10b32]">15</p>
|
||||
<p className="text-[11px] font-semibold text-slate-500">
|
||||
{t("rules.quick.cooldown")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-[#eefbf4] p-3">
|
||||
<p className="text-lg font-black text-[#168a4a]">1</p>
|
||||
<p className="text-[11px] font-semibold text-slate-500">
|
||||
{t("rules.quick.snapshot")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{prizeRows.map((row) => (
|
||||
) : (
|
||||
<div
|
||||
key={row.label}
|
||||
className="flex items-center justify-between rounded-xl border border-[#e6edf7] bg-white px-3 py-2 text-sm"
|
||||
>
|
||||
<span className="font-semibold text-slate-700">
|
||||
{t(row.label)}
|
||||
</span>
|
||||
<Badge variant="secondary">{row.count}</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="grid gap-3">
|
||||
{playSections.map((section) => (
|
||||
<Card key={section.title} className="border-[#e1e8f2] bg-white shadow-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-black text-slate-900">
|
||||
{t(section.title)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2">
|
||||
{section.items.map((item) => (
|
||||
<li
|
||||
key={item}
|
||||
className="rounded-xl bg-[#f8fafc] px-3 py-2 text-sm leading-6 text-slate-700"
|
||||
>
|
||||
{t(item)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Card className="border-[#d9e5f5] bg-[#0b3f96] text-white shadow-sm">
|
||||
<CardContent className="space-y-3 p-4">
|
||||
<div className="flex gap-3">
|
||||
<ShieldCheck className="mt-0.5 size-5 shrink-0" aria-hidden />
|
||||
<p className="text-sm leading-6 text-white/90">
|
||||
{t("rules.footer.config")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<TimerReset className="mt-0.5 size-5 shrink-0" aria-hidden />
|
||||
<p className="text-sm leading-6 text-white/90">
|
||||
{t("rules.footer.phaseTwo")}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/hall"
|
||||
className="inline-flex w-full items-center justify-center rounded-full bg-white px-4 py-2 text-sm font-black text-[#0b3f96]"
|
||||
>
|
||||
{t("rules.footer.backBet")}
|
||||
</Link>
|
||||
className="prose prose-sm max-w-none text-slate-700"
|
||||
dangerouslySetInnerHTML={{ __html: htmlContent || "" }}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user