diff --git a/enjoy/src/api/client.ts b/enjoy/src/api/client.ts index ec6763c3..cd796397 100644 --- a/enjoy/src/api/client.ts +++ b/enjoy/src/api/client.ts @@ -297,4 +297,28 @@ export class Client { unstarStory(storyId: string): Promise<{ starred: boolean }> { return this.api.delete(`/api/mine/stories/${storyId}`); } + + createPayment(params: { + amount: number; + processor: string; + paymentType: string; + }): Promise { + return this.api.post("/api/payments", decamelizeKeys(params)); + } + + payments(params?: { + paymentType?: string; + page?: number; + items?: number; + }): Promise< + { + payments: PaymentType[]; + } & PagyResponseType + > { + return this.api.get("/api/payments", { params: decamelizeKeys(params) }); + } + + payment(id: string): Promise { + return this.api.get(`/api/payments/${id}`); + } } diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 78e1e6f1..01504b39 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -309,6 +309,15 @@ "language": "Language", "balance": "Balance", "deposit": "Deposit", + "depositDescription": "Deposit to your account", + "depositDisclaimer": "After deposit, your balance is only used for paid services. It is not refundable.", + "pay": "Pay", + "pleaseCompletePaymentInPopupWindow": "Please complete payment in the popup window", + "processor": "Processor", + "amount": "Amount", + "date": "Date", + "status": "Status", + "recentDeposits": "Recent deposits", "notAvailableYet": "Not available yet", "whisperModel": "Whisper Model", "sttAiService": "STT AI service", diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index da86749e..fb727cfd 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -308,6 +308,15 @@ "language": "语言", "balance": "余额", "deposit": "充值", + "depositDescription": "充值至您的账户,用于购买付费服务", + "depositDisclaimer": "充值金额将直接存入您的账户,仅用于 App 内购买付费服务,不可退款", + "pay": "支付", + "pleaseCompletePaymentInPopupWindow": "请在弹出窗口中完成支付", + "processor": "渠道", + "amount": "金额", + "date": "日期", + "status": "状态", + "recentDeposits": "最近充值记录", "notAvailableYet": "暂未开放", "whisperModel": "Whisper 模型", "sttAiService": "语音转文本服务", diff --git a/enjoy/src/main/window.ts b/enjoy/src/main/window.ts index 15e16afd..d08a354e 100644 --- a/enjoy/src/main/window.ts +++ b/enjoy/src/main/window.ts @@ -20,7 +20,7 @@ import { WEB_API_URL, REPO_URL } from "@/constants"; import { AudibleProvider, TedProvider } from "@main/providers"; import Ffmpeg from "@main/ffmpeg"; import { Waveform } from "./waveform"; -import url from 'url'; +import url from "url"; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -442,14 +442,8 @@ ${log} }, }); - mainWindow.webContents.setWindowOpenHandler(({ url }) => { - if (url.startsWith("http")) { - logger.info(`Opening ${url}`); - shell.openExternal(url); - return { action: "deny" }; - } else { - return { action: "allow" }; - } + mainWindow.webContents.setWindowOpenHandler(() => { + return { action: "allow" }; }); // and load the index.html of the app. diff --git a/enjoy/src/renderer/components/preferences/balance-settings.tsx b/enjoy/src/renderer/components/preferences/balance-settings.tsx index cb0c060a..3f2f4933 100644 --- a/enjoy/src/renderer/components/preferences/balance-settings.tsx +++ b/enjoy/src/renderer/components/preferences/balance-settings.tsx @@ -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(0); + const [depositAmount, setDepositAmount] = useState(1); + const [loading, setLoading] = useState(false); + const [paymentCreated, setPaymentCreated] = useState(false); + const [payments, setPayments] = useState([]); - 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 = () => {
{t("balance")}
${balance}
- + + + + + + + {t("deposit")} + {t("depositDescription")} + + + {paymentCreated ? ( + <> + +
+ {t("pleaseCompletePaymentInPopupWindow")} +
+ + ) : ( + <> +
+ {[1, 2, 5, 10].map((amount) => ( +
setDepositAmount(amount)} + > + ${amount} +
+ ))} +
+
{t("depositDisclaimer")}
+ + )} + + + + + + {paymentCreated ? null : ( + <> + {user.hasMixin && ( + + )} + + + )} + + + {payments.length > 0 && ( +
+ + {t("recentDeposits")} + + + ID + {t("amount")} + {t("status")} + + {t("processor")} + + {t("date")} + + + + {payments.map((payment) => ( + + + + {payment.id.split("-").shift()} + + + ${payment.amount} + {payment.status} + + {payment.processor} + + + {formatDateTime(payment.createdAt)} + + + ))} + +
+
+ )} +
+ ); }; diff --git a/enjoy/src/renderer/context/app-settings-provider.tsx b/enjoy/src/renderer/context/app-settings-provider.tsx index 2299509e..cdc7f5dc 100644 --- a/enjoy/src/renderer/context/app-settings-provider.tsx +++ b/enjoy/src/renderer/context/app-settings-provider.tsx @@ -150,7 +150,7 @@ export const AppSettingsProvider = ({ client.me().then((user) => { if (user?.id) { - login(currentUser); + login(Object.assign({}, currentUser, user)); } else { logout(); } diff --git a/enjoy/src/types/payment.d.ts b/enjoy/src/types/payment.d.ts new file mode 100644 index 00000000..771efc57 --- /dev/null +++ b/enjoy/src/types/payment.d.ts @@ -0,0 +1,14 @@ +type PaymentType = { + id: string; + amount: number; + status: 'succeeded' | 'expired' | 'pending'; + paymentType: string; + processor: string; + traceId?: string; + snapshotId?: string; + memo?: string; + payUrl?: string; + receiptUrl?: string; + createdAt: string; + updatedAt: string; +} diff --git a/enjoy/src/types/user.d.ts b/enjoy/src/types/user.d.ts index d0a71fb9..4ebca7d8 100644 --- a/enjoy/src/types/user.d.ts +++ b/enjoy/src/types/user.d.ts @@ -6,5 +6,6 @@ type UserType = { accessToken?: string; recordingsCount?: number; recordingsDuration?: number; + hasMixin?: boolean; createdAt?: string; };