Feat: integrate deposit (#363)

* create deposit payment

* display recent deposits & improve UI

* support Mixin pay
This commit is contained in:
an-lee
2024-02-28 15:29:07 +08:00
committed by GitHub
parent 350dcf1897
commit ded7371be6
8 changed files with 257 additions and 21 deletions

View File

@@ -1,16 +1,82 @@
import { t } from "i18next";
import { AppSettingsProviderContext } from "@renderer/context";
import { useContext, useState, useEffect } from "react";
import { Button } from "@renderer/components/ui";
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogClose,
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
toast,
} from "@renderer/components/ui";
import { LoaderSpin } from "@renderer/components";
import { LoaderIcon } from "lucide-react";
import { formatDateTime } from "@/renderer/lib/utils";
export const BalanceSettings = () => {
const { webApi } = useContext(AppSettingsProviderContext);
const { webApi, user } = useContext(AppSettingsProviderContext);
const [balance, setBalance] = useState<number>(0);
const [depositAmount, setDepositAmount] = useState<number>(1);
const [loading, setLoading] = useState<boolean>(false);
const [paymentCreated, setPaymentCreated] = useState<boolean>(false);
const [payments, setPayments] = useState<any[]>([]);
useEffect(() => {
const refreshPayments = () => {
webApi
.payments({
paymentType: "deposit",
})
.then(({ payments }) => {
setPayments(payments);
})
.catch((error) => {
toast.error(error.message);
});
};
const refreshBalance = () => {
webApi.me().then((user) => {
setBalance(user.balance);
});
};
const createDepositPayment = (processor = "stripe") => {
if (loading) return;
setLoading(true);
webApi
.createPayment({
amount: depositAmount,
paymentType: "deposit",
processor,
})
.then((payment) => {
if (payment?.payUrl) {
setPaymentCreated(true);
window.open(payment.payUrl, "model");
}
})
.catch((error) => {
toast.error(error.message);
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
refreshBalance();
}, []);
if (!balance) return null;
@@ -20,15 +86,134 @@ export const BalanceSettings = () => {
<div className="mb-2">{t("balance")}</div>
<div className="text-sm text-muted-foreground mb-2">${balance}</div>
</div>
<Button
data-tooltip-id="preferences-tooltip"
data-tooltip-content={t("notAvailableYet")}
variant="secondary"
size="sm"
className="cursor-not-allowed"
<Dialog
onOpenChange={(value) => {
if (value) {
refreshPayments();
} else {
setPaymentCreated(false);
refreshBalance();
}
}}
>
{t("deposit")}
</Button>
<DialogTrigger asChild>
<Button
onClick={() => setPaymentCreated(false)}
variant="secondary"
size="sm"
className=""
>
{t("deposit")}
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{t("deposit")}</DialogTitle>
<DialogDescription>{t("depositDescription")}</DialogDescription>
</DialogHeader>
{paymentCreated ? (
<>
<LoaderSpin />
<div className="text-center">
{t("pleaseCompletePaymentInPopupWindow")}
</div>
</>
) : (
<>
<div className="grid grid-cols-4 gap-4">
{[1, 2, 5, 10].map((amount) => (
<div
className={`text-xl w-full h-20 border rounded-md flex items-center justify-center cursor-pointer shadow hover:bg-gray-100 transition-colors duration-200 ease-in-out ${
amount == depositAmount ? "bg-gray-100" : ""
}`}
key={`deposit-amount-${amount}`}
onClick={() => setDepositAmount(amount)}
>
${amount}
</div>
))}
</div>
<div className="text-sm">{t("depositDisclaimer")}</div>
</>
)}
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">
{paymentCreated ? t("finish") : t("cancel")}
</Button>
</DialogClose>
{paymentCreated ? null : (
<>
{user.hasMixin && (
<Button
variant="default"
disabled={loading}
className="bg-blue-500 hover:bg-blue-600 transition-colors duration-200 ease-in-out"
onClick={() => createDepositPayment("mixin")}
>
{loading && (
<LoaderIcon className="w-4 h-4 mr-2 animate-spin" />
)}
<span>Mixin {t('pay')}</span>
</Button>
)}
<Button
variant="default"
disabled={loading}
onClick={() => createDepositPayment()}
>
{loading && (
<LoaderIcon className="w-4 h-4 mr-2 animate-spin" />
)}
<span>{t("pay")}</span>
</Button>
</>
)}
</DialogFooter>
{payments.length > 0 && (
<div className="">
<Table>
<TableCaption>{t("recentDeposits")}</TableCaption>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead className="capitalize">{t("amount")}</TableHead>
<TableHead className="capitalize">{t("status")}</TableHead>
<TableHead className="capitalize">
{t("processor")}
</TableHead>
<TableHead className="capitalize">{t("date")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{payments.map((payment) => (
<TableRow key={payment.id}>
<TableCell>
<span className="text-xs bg-muted font-mono p-0.5 rounded select-text">
{payment.id.split("-").shift()}
</span>
</TableCell>
<TableCell>${payment.amount}</TableCell>
<TableCell className="">{payment.status}</TableCell>
<TableCell className="capitalize">
{payment.processor}
</TableCell>
<TableCell className="text-xs">
{formatDateTime(payment.createdAt)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</DialogContent>
</Dialog>
</div>
);
};

View File

@@ -150,7 +150,7 @@ export const AppSettingsProvider = ({
client.me().then((user) => {
if (user?.id) {
login(currentUser);
login(Object.assign({}, currentUser, user));
} else {
logout();
}