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:
@@ -25,15 +25,6 @@ type BaseProps = {
|
||||
idPrefix?: string;
|
||||
};
|
||||
|
||||
const defaultInTrigger =
|
||||
"!bg-[#0b3f96] !text-white shadow-[0_8px_18px_rgba(11,63,150,0.18)] hover:!bg-[#08357f]";
|
||||
const defaultOutTrigger = "flex-1";
|
||||
|
||||
const hallInTrigger =
|
||||
"rounded-2xl border border-[#d7e5fb] !bg-[#0b3f96] !text-white shadow-[0_10px_24px_rgba(11,63,150,0.22)] hover:!bg-[#08357f]";
|
||||
const hallOutTrigger =
|
||||
"rounded-2xl border border-[#ffc7d2] !bg-white !text-[#e5002c] shadow-[0_8px_22px_rgba(216,20,53,0.08)] hover:!bg-[#fff5f7]";
|
||||
|
||||
export function TransferInDialog({
|
||||
currency,
|
||||
lotteryMinor,
|
||||
@@ -52,31 +43,29 @@ export function TransferInDialog({
|
||||
const { t } = useTranslation("player");
|
||||
const resolvedTriggerLabel = triggerLabel ?? t("wallet.transferIn");
|
||||
|
||||
const triggerCombined = cn(
|
||||
"inline-flex h-10 min-h-10 w-full min-w-0 flex-1 items-center justify-center gap-1.5 px-3 text-sm font-medium",
|
||||
triggerVariant === "hall" ? hallInTrigger : defaultInTrigger,
|
||||
triggerClassName,
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className={triggerCombined}
|
||||
variant="default"
|
||||
className={cn(
|
||||
"inline-flex h-10 min-h-10 w-full min-w-0 flex-1 items-center justify-center gap-1.5 px-3 text-sm font-medium",
|
||||
triggerVariant === "hall" && "bg-[#07459f] text-white hover:bg-[#063b88]",
|
||||
triggerClassName,
|
||||
)}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<ArrowDownLeft className="size-4 shrink-0" />
|
||||
{resolvedTriggerLabel}
|
||||
</Button>
|
||||
<DialogContent showCloseButton className="overflow-hidden rounded-[28px] border border-[#dbe7fb] bg-white p-0 shadow-[0_24px_70px_rgba(11,63,150,0.18)] sm:max-w-[430px] [&_[data-slot=dialog-close]]:right-3 [&_[data-slot=dialog-close]]:top-3 [&_[data-slot=dialog-close]]:text-white [&_[data-slot=dialog-close]]:hover:bg-white/15 [&_[data-slot=dialog-close]]:hover:text-white">
|
||||
<DialogHeader className="bg-gradient-to-br from-[#0b3f96] via-[#1456bd] to-[#e5002c] px-5 pb-5 pt-6 text-white">
|
||||
<DialogTitle className="text-xl font-black text-white">{t("wallet.transferInTitle")}</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-6 text-white/85">
|
||||
<DialogContent showCloseButton className="gap-0 overflow-hidden p-0 sm:max-w-md">
|
||||
<DialogHeader className="space-y-1.5 border-b border-border px-5 py-4 text-left">
|
||||
<DialogTitle className="text-lg font-semibold">{t("wallet.transferInTitle")}</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-relaxed">
|
||||
{t("wallet.dialogInDescription", { currency })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="px-5 pb-5 pt-4">
|
||||
<div className="px-5 py-4">
|
||||
<TransferInPanel
|
||||
variant="dialog"
|
||||
currency={currency}
|
||||
@@ -112,36 +101,29 @@ export function TransferOutDialog({
|
||||
const { t } = useTranslation("player");
|
||||
const resolvedTriggerLabel = triggerLabel ?? t("wallet.transferOut");
|
||||
|
||||
const triggerCombined = cn(
|
||||
"inline-flex h-10 min-h-10 w-full min-w-0 flex-1 items-center justify-center gap-1.5 px-3 text-sm font-medium",
|
||||
triggerVariant === "hall"
|
||||
? hallOutTrigger
|
||||
: cn(
|
||||
"!border-2 !border-input !bg-secondary !text-secondary-foreground hover:!bg-secondary/80",
|
||||
defaultOutTrigger,
|
||||
),
|
||||
triggerClassName,
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className={triggerCombined}
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"inline-flex h-10 min-h-10 w-full min-w-0 flex-1 items-center justify-center gap-1.5 px-3 text-sm font-medium",
|
||||
triggerVariant === "hall" && "border-border bg-card hover:bg-muted",
|
||||
triggerClassName,
|
||||
)}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<ArrowUpRight className="size-4 shrink-0" />
|
||||
{resolvedTriggerLabel}
|
||||
</Button>
|
||||
<DialogContent showCloseButton className="overflow-hidden rounded-[28px] border border-[#ffd7df] bg-white p-0 shadow-[0_24px_70px_rgba(216,20,53,0.16)] sm:max-w-[430px] [&_[data-slot=dialog-close]]:right-3 [&_[data-slot=dialog-close]]:top-3 [&_[data-slot=dialog-close]]:text-white [&_[data-slot=dialog-close]]:hover:bg-white/15 [&_[data-slot=dialog-close]]:hover:text-white">
|
||||
<DialogHeader className="bg-gradient-to-br from-[#e5002c] via-[#d81435] to-[#0b3f96] px-5 pb-5 pt-6 text-white">
|
||||
<DialogTitle className="text-xl font-black text-white">{t("wallet.transferOutTitle")}</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-6 text-white/85">
|
||||
<DialogContent showCloseButton className="gap-0 overflow-hidden p-0 sm:max-w-md">
|
||||
<DialogHeader className="space-y-1.5 border-b border-border px-5 py-4 text-left">
|
||||
<DialogTitle className="text-lg font-semibold">{t("wallet.transferOutTitle")}</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-relaxed">
|
||||
{t("wallet.dialogOutDescription")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="px-5 pb-5 pt-4">
|
||||
<div className="px-5 py-4">
|
||||
<TransferOutPanel
|
||||
variant="dialog"
|
||||
currency={currency}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user