add usages (#973)
This commit is contained in:
@@ -247,6 +247,10 @@ export class Client {
|
||||
});
|
||||
}
|
||||
|
||||
usages(): Promise<{ label: string; data: number[] }[]> {
|
||||
return this.api.get("/api/mine/usages");
|
||||
}
|
||||
|
||||
syncAudio(audio: Partial<AudioType>) {
|
||||
return this.api.post("/api/mine/audios", decamelizeKeys(audio));
|
||||
}
|
||||
|
||||
@@ -718,5 +718,6 @@
|
||||
"audioInput": "Audio input",
|
||||
"textInput": "Text input",
|
||||
"increasePlaybackRate": "Increase playback rate",
|
||||
"descreasePlaybackRate": "Descrease playback rate"
|
||||
"descreasePlaybackRate": "Descrease playback rate",
|
||||
"usage": "Usage"
|
||||
}
|
||||
|
||||
@@ -718,5 +718,6 @@
|
||||
"audioInput": "语音输入",
|
||||
"textInput": "文字输入",
|
||||
"increasePlaybackRate": "加快播放速度",
|
||||
"decreasePlaybackRate": "减慢播放速度"
|
||||
"decreasePlaybackRate": "减慢播放速度",
|
||||
"usage": "使用情况"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { t } from "i18next";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { useContext, useState, useEffect } from "react";
|
||||
import { useContext, useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import { LoaderSpin } from "@renderer/components";
|
||||
import { LoaderIcon } from "lucide-react";
|
||||
import { formatDateTime } from "@/renderer/lib/utils";
|
||||
import Chart from "chart.js/auto";
|
||||
|
||||
export const BalanceSettings = () => {
|
||||
const { webApi, EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
@@ -32,6 +33,7 @@ export const BalanceSettings = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [paymentCreated, setPaymentCreated] = useState<boolean>(false);
|
||||
const [payments, setPayments] = useState<any[]>([]);
|
||||
const [displayUsage, setDisplayUsage] = useState<boolean>(false);
|
||||
|
||||
const refreshPayments = () => {
|
||||
webApi
|
||||
@@ -88,150 +90,234 @@ export const BalanceSettings = () => {
|
||||
<div className="text-sm text-muted-foreground mb-2">${balance}</div>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
onOpenChange={(value) => {
|
||||
if (value) {
|
||||
refreshPayments();
|
||||
} else {
|
||||
setPaymentCreated(false);
|
||||
refreshBalance();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
onClick={() => setPaymentCreated(false)}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className=""
|
||||
>
|
||||
{t("deposit")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-h-full overflow-y-auto">
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center w-64 justify-around">
|
||||
<img src="assets/usdt.png" className="w-auto h-8 rounded-full" />
|
||||
<img src="assets/usdc.png" className="w-auto h-8" />
|
||||
<img src="assets/eth.png" className="w-auto h-8" />
|
||||
<img src="assets/trx.png" className="w-auto h-8" />
|
||||
<img src="assets/doge.png" className="w-auto h-8" />
|
||||
<img src="assets/bnb.png" className="w-auto h-8" />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Dialog
|
||||
onOpenChange={(value) => {
|
||||
if (value) {
|
||||
refreshPayments();
|
||||
} else {
|
||||
setPaymentCreated(false);
|
||||
refreshBalance();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
disabled={paymentCreated || loading}
|
||||
className="w-32 bg-blue-500 hover:bg-blue-600 transition-colors duration-200 ease-in-out"
|
||||
onClick={() => createDepositPayment("mixin")}
|
||||
onClick={() => setPaymentCreated(false)}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className=""
|
||||
>
|
||||
{loading && <LoaderIcon className="w-4 h-4 mr-2 animate-spin" />}
|
||||
<span>Crypto {t("pay")}</span>
|
||||
{t("deposit")}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center w-64 justify-around">
|
||||
<img src="assets/mastercard.png" className="w-auto h-8" />
|
||||
<img src="assets/visa.png" className="w-auto h-8" />
|
||||
<img src="assets/unionpay.png" className="w-auto h-8" />
|
||||
<img src="assets/alipay.png" className="w-auto h-8" />
|
||||
<img src="assets/wechatpay.png" className="w-auto h-8" />
|
||||
</div>
|
||||
<Button
|
||||
className="w-32"
|
||||
variant="default"
|
||||
disabled={paymentCreated || loading}
|
||||
onClick={() => createDepositPayment()}
|
||||
>
|
||||
{loading && <LoaderIcon className="w-4 h-4 mr-2 animate-spin" />}
|
||||
<span>Stripe {t("pay")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<DialogContent className="max-h-full overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("deposit")}</DialogTitle>
|
||||
<DialogDescription>{t("depositDescription")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Separator />
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary">
|
||||
{paymentCreated ? t("finish") : t("cancel")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</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>
|
||||
{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>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="text-sm">{t("depositDisclaimer")}</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center w-64 justify-around">
|
||||
<img
|
||||
src="assets/usdt.png"
|
||||
className="w-auto h-8 rounded-full"
|
||||
/>
|
||||
<img src="assets/usdc.png" className="w-auto h-8" />
|
||||
<img src="assets/eth.png" className="w-auto h-8" />
|
||||
<img src="assets/trx.png" className="w-auto h-8" />
|
||||
<img src="assets/doge.png" className="w-auto h-8" />
|
||||
<img src="assets/bnb.png" className="w-auto h-8" />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="default"
|
||||
disabled={paymentCreated || loading}
|
||||
className="w-32 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>Crypto {t("pay")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center w-64 justify-around">
|
||||
<img src="assets/mastercard.png" className="w-auto h-8" />
|
||||
<img src="assets/visa.png" className="w-auto h-8" />
|
||||
<img src="assets/unionpay.png" className="w-auto h-8" />
|
||||
<img src="assets/alipay.png" className="w-auto h-8" />
|
||||
<img src="assets/wechatpay.png" className="w-auto h-8" />
|
||||
</div>
|
||||
<Button
|
||||
className="w-32"
|
||||
variant="default"
|
||||
disabled={paymentCreated || loading}
|
||||
onClick={() => createDepositPayment()}
|
||||
>
|
||||
{loading && (
|
||||
<LoaderIcon className="w-4 h-4 mr-2 animate-spin" />
|
||||
)}
|
||||
<span>Stripe {t("pay")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary">
|
||||
{paymentCreated ? t("finish") : t("cancel")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</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>
|
||||
|
||||
<Dialog open={displayUsage} onOpenChange={setDisplayUsage}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary" size="sm" className="">
|
||||
{t("usage")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-full overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("usage")}</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
Usage rencently
|
||||
</DialogDescription>
|
||||
{displayUsage && <UsageChart />}
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UsageChart = () => {
|
||||
const { webApi, EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const [usages, setUsages] = useState<{ label: string; data: number[] }[]>([]);
|
||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const fetchUsages = () => {
|
||||
webApi
|
||||
.usages()
|
||||
.then((usages) => {
|
||||
setUsages(usages);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
|
||||
const renderUsageChart = () => {
|
||||
if (!chartRef.current) return;
|
||||
if (!usages.length) return;
|
||||
|
||||
new Chart(chartRef.current, {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: Object.keys(usages[0].data),
|
||||
datasets: usages.map((usage) => ({
|
||||
label: usage.label,
|
||||
data: Object.values(usage.data),
|
||||
})),
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchUsages();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
renderUsageChart();
|
||||
}, [usages, chartRef.current]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<canvas ref={chartRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user