refactor: enhance wallet transfer dialogs and forms

- Removed unused trigger styles and refactored button variants for improved consistency.
- Introduced new components for transfer information, previews, and error handling to streamline the UI.
- Updated layout and styling for better user experience in transfer dialogs and forms.
This commit is contained in:
2026-05-21 16:33:32 +08:00
parent 6b18e25766
commit 496ed10981
2 changed files with 138 additions and 135 deletions

View File

@@ -2,7 +2,7 @@
import { isAxiosError } from "axios";
import { Loader2 } from "lucide-react";
import { useMemo, useState } from "react";
import { useMemo, useState, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@@ -55,6 +55,73 @@ type PanelBase = {
type PanelVariant = "dialog" | "page";
function TransferInfoBlock({ children }: { children: ReactNode }) {
return (
<div className="rounded-lg border border-border bg-muted/40 px-4 py-3 text-sm leading-relaxed">
{children}
</div>
);
}
function TransferPreview({ children }: { children: ReactNode }) {
return (
<p className="rounded-lg bg-muted/50 px-3 py-2 text-sm text-muted-foreground">{children}</p>
);
}
function TransferError({ message }: { message: string }) {
return (
<p className="rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{message}
</p>
);
}
function TransferDialogFooter({
submitting,
confirmLabel,
onConfirm,
onCancel,
}: {
submitting: boolean;
confirmLabel: string;
onConfirm: () => void;
onCancel: () => void;
}) {
const { t } = useTranslation("player");
return (
<div className="mt-5 grid gap-2">
<Button
type="button"
size="lg"
className="h-11 w-full bg-[#07459f] text-base font-semibold text-white hover:bg-[#063b88]"
disabled={submitting}
onClick={onConfirm}
>
{submitting ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
{t("actions.processing")}
</>
) : (
confirmLabel
)}
</Button>
<Button
type="button"
variant="outline"
size="lg"
className="h-10 w-full"
disabled={submitting}
onClick={onCancel}
>
{t("actions.cancel")}
</Button>
</div>
);
}
/** 弹窗内:取消关闭;独立页:仅展示提交(返回用顶栏或上方链接) */
export function TransferInPanel({
currency,
@@ -113,7 +180,8 @@ export function TransferInPanel({
variant === "page" ? (
<Button
type="button"
className="h-11 w-full rounded-lg bg-[#07459f] text-base font-bold text-white hover:bg-[#063b88]"
size="lg"
className="mt-5 h-11 w-full bg-[#07459f] text-base font-semibold text-white hover:bg-[#063b88]"
disabled={submitting}
onClick={() => void submit()}
>
@@ -127,51 +195,31 @@ export function TransferInPanel({
)}
</Button>
) : (
<div className="grid gap-2">
<Button
type="button"
className="h-12 w-full rounded-2xl bg-[#0b3f96] text-base font-black text-white shadow-[0_10px_22px_rgba(11,63,150,0.18)] hover:bg-[#08357f]"
disabled={submitting}
onClick={() => void submit()}
>
{submitting ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
{t("actions.processing")}
</>
) : (
t("wallet.confirmIn")
)}
</Button>
<Button
type="button"
variant="outline"
className="h-11 rounded-2xl border-[#dce7f7] bg-white text-base font-bold text-[#32518d] hover:bg-[#f8fbff]"
disabled={submitting}
onClick={onCancel}
>
{t("actions.cancel")}
</Button>
</div>
<TransferDialogFooter
submitting={submitting}
confirmLabel={t("wallet.confirmIn")}
onConfirm={() => void submit()}
onCancel={onCancel}
/>
);
return (
<>
<div className="grid gap-4 py-1">
<div className="rounded-2xl border border-[#dbe7fb] bg-gradient-to-br from-[#f8fbff] to-white px-4 py-3 text-sm shadow-[0_8px_22px_rgba(11,63,150,0.06)]">
<p className="text-slate-500">
<div className="grid gap-4">
<TransferInfoBlock>
<p className="text-muted-foreground">
{t("wallet.mainBalance")}{" "}
<span className="font-semibold text-slate-700">{t("wallet.mainPending")}</span>
<span className="font-medium text-foreground">{t("wallet.mainPending")}</span>
</p>
<p className="mt-2 text-slate-500">
<p className="mt-2 text-muted-foreground">
{t("wallet.lotteryBalance")}{" "}
<span className="font-black tabular-nums text-[#0b3f96]">
<span className="font-semibold tabular-nums text-foreground">
{formatMinorAsCurrency(lotteryMinor, currency)}
</span>
</p>
</div>
<div className="grid gap-2.5">
<Label htmlFor={tid} className="text-sm font-black text-[#101a33]">{t("wallet.inAmount")}</Label>
</TransferInfoBlock>
<div className="grid gap-2">
<Label htmlFor={tid}>{t("wallet.inAmount")}</Label>
<Input
id={tid}
inputMode="decimal"
@@ -180,17 +228,15 @@ export function TransferInPanel({
onChange={(ev) => setAmountText(ev.target.value)}
disabled={submitting}
autoComplete="off"
className="h-13 rounded-2xl border-[#d7e5fb] bg-white px-4 text-lg font-bold tabular-nums text-[#101a33] shadow-inner focus-visible:ring-[#0b3f96]/20"
className="h-11 text-base tabular-nums"
/>
<p className="rounded-2xl bg-[#f8fbff] px-3 py-2 text-xs font-semibold leading-5 text-[#32518d]">
<TransferPreview>
{t("wallet.afterInPreview", {
amount: formatMinorAsCurrency(previewAfter, currency),
})}
</p>
</TransferPreview>
</div>
{localError ? (
<p className="rounded-2xl border border-red-200 bg-red-50 px-3 py-2 text-sm font-semibold text-[#d81435]">{localError}</p>
) : null}
{localError ? <TransferError message={localError} /> : null}
</div>
{footer}
</>
@@ -266,7 +312,8 @@ export function TransferOutPanel({
variant === "page" ? (
<Button
type="button"
className="h-11 w-full rounded-lg bg-[#e5002c] text-base font-bold text-white hover:bg-[#d10028]"
size="lg"
className="mt-5 h-11 w-full bg-[#07459f] text-base font-semibold text-white hover:bg-[#063b88]"
disabled={submitting}
onClick={() => void submit()}
>
@@ -280,52 +327,32 @@ export function TransferOutPanel({
)}
</Button>
) : (
<div className="grid gap-2">
<Button
type="button"
className="h-12 w-full rounded-2xl bg-[#e5002c] text-base font-black text-white shadow-[0_10px_22px_rgba(229,0,44,0.18)] hover:bg-[#d10028]"
disabled={submitting}
onClick={() => void submit()}
>
{submitting ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
{t("actions.processing")}
</>
) : (
t("wallet.confirmOut")
)}
</Button>
<Button
type="button"
variant="outline"
className="h-11 rounded-2xl border-[#ffd7df] bg-white text-base font-bold text-[#d81435] hover:bg-[#fff5f7]"
disabled={submitting}
onClick={onCancel}
>
{t("actions.cancel")}
</Button>
</div>
<TransferDialogFooter
submitting={submitting}
confirmLabel={t("wallet.confirmOut")}
onConfirm={() => void submit()}
onCancel={onCancel}
/>
);
return (
<>
<div className="grid gap-4 py-1">
<div className="rounded-2xl border border-[#ffd7df] bg-gradient-to-br from-[#fff7f8] to-white px-4 py-3 text-sm shadow-[0_8px_22px_rgba(216,20,53,0.06)]">
<p className="text-slate-500">
<div className="grid gap-4">
<TransferInfoBlock>
<p className="text-muted-foreground">
{t("wallet.lotteryAvailable")}{" "}
<span className="font-black tabular-nums text-[#d81435]">
<span className="font-semibold tabular-nums text-foreground">
{formatMinorAsCurrency(availableMinor, currency)}
</span>
</p>
</div>
<div className="grid gap-2.5">
<div className="flex items-end justify-between gap-2">
<Label htmlFor={tid} className="text-sm font-black text-[#101a33]">{t("wallet.outAmount")}</Label>
</TransferInfoBlock>
<div className="grid gap-2">
<div className="flex items-center justify-between gap-2">
<Label htmlFor={tid}>{t("wallet.outAmount")}</Label>
<Button
type="button"
variant="link"
className="h-auto p-0 text-xs font-black text-[#d81435] hover:text-[#b80f2b]"
className="h-auto p-0 text-sm font-medium"
onClick={fillAll}
disabled={submitting || availableMinor < 1}
>
@@ -342,17 +369,15 @@ export function TransferOutPanel({
onChange={(ev) => setAmountText(ev.target.value)}
disabled={submitting}
autoComplete="off"
className="h-13 rounded-2xl border-[#ffd7df] bg-white px-4 text-lg font-bold tabular-nums text-[#101a33] shadow-inner focus-visible:ring-[#e5002c]/20"
className="h-11 text-base tabular-nums"
/>
<p className="rounded-2xl bg-[#fff7f8] px-3 py-2 text-xs font-semibold leading-5 text-[#9f1730]">
<TransferPreview>
{t("wallet.afterOutPreview", {
amount: formatMinorAsCurrency(previewAfter, currency),
})}
</p>
</TransferPreview>
</div>
{localError ? (
<p className="rounded-2xl border border-red-200 bg-red-50 px-3 py-2 text-sm font-semibold text-[#d81435]">{localError}</p>
) : null}
{localError ? <TransferError message={localError} /> : null}
</div>
{footer}
</>
@@ -375,12 +400,10 @@ export function TransferInPage({
backHref="/wallet"
backLabel={t("wallet.title")}
>
<Card className="rounded-xl border-[#e5edf8] shadow-[0_8px_24px_rgba(15,23,42,0.05)]">
<Card>
<CardHeader>
<CardTitle className="text-[#0b3f96]">{t("wallet.transferInTitle")}</CardTitle>
<CardDescription>
{t("wallet.transferInDescription")}
</CardDescription>
<CardTitle>{t("wallet.transferInTitle")}</CardTitle>
<CardDescription>{t("wallet.transferInDescription")}</CardDescription>
</CardHeader>
<CardContent>
<TransferInPanel
@@ -413,12 +436,10 @@ export function TransferOutPage({
backHref="/wallet"
backLabel={t("wallet.title")}
>
<Card className="rounded-xl border-[#e5edf8] shadow-[0_8px_24px_rgba(15,23,42,0.05)]">
<Card>
<CardHeader>
<CardTitle className="text-[#0b3f96]">{t("wallet.transferOutTitle")}</CardTitle>
<CardDescription>
{t("wallet.transferOutDescription")}
</CardDescription>
<CardTitle>{t("wallet.transferOutTitle")}</CardTitle>
<CardDescription>{t("wallet.transferOutDescription")}</CardDescription>
</CardHeader>
<CardContent>
<TransferOutPanel