mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
web: allow users to view their billing history
This commit is contained in:
committed by
Abdullah Atta
parent
7f873f240e
commit
2bd0f1a304
@@ -211,8 +211,9 @@ export function showError(title: string, message: string) {
|
||||
export function showMultiDeleteConfirmation(length: number) {
|
||||
return confirm({
|
||||
title: `Delete ${length} items?`,
|
||||
message: `These items will be **kept in your Trash for ${db.settings?.getTrashCleanupInterval() || 7
|
||||
} days** after which they will be permanently deleted.`,
|
||||
message: `These items will be **kept in your Trash for ${
|
||||
db.settings?.getTrashCleanupInterval() || 7
|
||||
} days** after which they will be permanently deleted.`,
|
||||
positiveButtonText: "Yes",
|
||||
negativeButtonText: "No"
|
||||
});
|
||||
@@ -331,6 +332,12 @@ export function showMoveNoteDialog(noteIds: string[]) {
|
||||
));
|
||||
}
|
||||
|
||||
export function showBillingHistoryDialog() {
|
||||
return showDialog("BillingHistoryDialog", (Dialog, perform) => (
|
||||
<Dialog onClose={(res: boolean) => perform(res)} />
|
||||
));
|
||||
}
|
||||
|
||||
function getDialogData(type: string) {
|
||||
switch (type) {
|
||||
case "create_vault":
|
||||
|
||||
145
apps/web/src/components/dialogs/billing-history-dialog.tsx
Normal file
145
apps/web/src/components/dialogs/billing-history-dialog.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Perform } from "../../common/dialog-controller";
|
||||
import Dialog from "./dialog";
|
||||
import { db } from "../../common/db";
|
||||
import { Loading } from "../icons";
|
||||
import { Flex, Link, Text } from "@theme-ui/components";
|
||||
import { formatDate } from "@notesnook/core/utils/date";
|
||||
|
||||
type Transaction = {
|
||||
order_id: string;
|
||||
checkout_id: string;
|
||||
amount: string;
|
||||
currency: string;
|
||||
status: keyof typeof TransactionStatusToText;
|
||||
created_at: Date;
|
||||
passthrough: null;
|
||||
product_id: number;
|
||||
is_subscription: boolean;
|
||||
is_one_off: boolean;
|
||||
subscription: Subscription;
|
||||
user: User;
|
||||
receipt_url: string;
|
||||
};
|
||||
|
||||
type Subscription = {
|
||||
subscription_id: number;
|
||||
status: string;
|
||||
};
|
||||
|
||||
type User = {
|
||||
user_id: number;
|
||||
email: string;
|
||||
marketing_consent: boolean;
|
||||
};
|
||||
|
||||
const TransactionStatusToText = {
|
||||
completed: "Completed",
|
||||
refunded: "Refunded",
|
||||
partially_refunded: "Partially refunded",
|
||||
disputed: "Disputed"
|
||||
};
|
||||
|
||||
export type BillingHistoryDialogProps = {
|
||||
onClose: Perform;
|
||||
};
|
||||
|
||||
export default function BillingHistoryDialog(props: BillingHistoryDialogProps) {
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
try {
|
||||
setError(undefined);
|
||||
setIsLoading(true);
|
||||
|
||||
const transactions = await db.subscriptions?.transactions();
|
||||
if (!transactions) return;
|
||||
setTransactions(transactions);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) setError(e);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={"Billing history"}
|
||||
description={"View all the transactions you have made with Notesnook."}
|
||||
onClose={() => props.onClose(false)}
|
||||
negativeButton={{ text: "Close", onClick: () => props.onClose(false) }}
|
||||
width={600}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loading sx={{ mt: 2 }} />
|
||||
) : error ? (
|
||||
<Flex sx={{ bg: "errorBg", p: 1, borderRadius: "default" }}>
|
||||
<Text variant="error">
|
||||
{error.message}
|
||||
<br />
|
||||
{error.stack}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex sx={{ flexDirection: "column", gap: 1, mt: 1 }}>
|
||||
{transactions.map((transaction) => (
|
||||
<Flex
|
||||
key={transaction.order_id}
|
||||
sx={{
|
||||
justifyContent: "space-between",
|
||||
borderBottom: "1px solid var(--border)",
|
||||
pb: 1
|
||||
}}
|
||||
>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text variant="subtitle">Order #{transaction.order_id}</Text>
|
||||
<Text variant="body" sx={{ color: "fontTertiary" }}>
|
||||
{formatDate(new Date(transaction.created_at).getTime())} •{" "}
|
||||
{TransactionStatusToText[transaction.status]}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex sx={{ flexDirection: "column", alignItems: "end" }}>
|
||||
<Text variant="body">
|
||||
{transaction.amount} {transaction.currency}
|
||||
</Text>
|
||||
<Link
|
||||
href={transaction.receipt_url}
|
||||
target="_blank"
|
||||
rel="noreferer nofollow"
|
||||
variant="text.subBody"
|
||||
sx={{ color: "primary" }}
|
||||
>
|
||||
View receipt
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -57,6 +57,9 @@ const EmailChangeDialog = React.lazy(() => import("./email-change-dialog"));
|
||||
const LanguageSelectorDialog = React.lazy(
|
||||
() => import("./language-selector-dialog")
|
||||
);
|
||||
const BillingHistoryDialog = React.lazy(
|
||||
() => import("./billing-history-dialog")
|
||||
);
|
||||
|
||||
export const Dialogs = {
|
||||
AddNotebookDialog,
|
||||
@@ -85,5 +88,6 @@ export const Dialogs = {
|
||||
AddReminderDialog,
|
||||
ReminderPreviewDialog,
|
||||
EmailChangeDialog,
|
||||
LanguageSelectorDialog
|
||||
LanguageSelectorDialog,
|
||||
BillingHistoryDialog
|
||||
};
|
||||
|
||||
@@ -44,7 +44,8 @@ import {
|
||||
showPromptDialog,
|
||||
showEmailChangeDialog,
|
||||
showLanguageSelectorDialog,
|
||||
confirm
|
||||
confirm,
|
||||
showBillingHistoryDialog
|
||||
} from "../common/dialog-controller";
|
||||
import { TaskManager } from "../common/task-manager";
|
||||
import { SUBSCRIPTION_STATUS } from "../common/constants";
|
||||
@@ -1391,6 +1392,12 @@ function AccountStatus(props) {
|
||||
tip="If you are eligible for a refund, your account will be immediately downgraded to Basic and your funds will be transferred to your account within 24 hours."
|
||||
/>
|
||||
</Button>
|
||||
<Button variant="list" onClick={() => showBillingHistoryDialog()}>
|
||||
<Tip
|
||||
text="Billing history"
|
||||
tip="View all the transactions you have made with Notesnook."
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
variant="list"
|
||||
sx={{ ":hover": { borderColor: "error" } }}
|
||||
@@ -1413,11 +1420,8 @@ function AccountStatus(props) {
|
||||
});
|
||||
showToast(
|
||||
"success",
|
||||
"Your subscription has been cancelled."
|
||||
"Your subscription has been canceled."
|
||||
);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
}
|
||||
} catch (e) {
|
||||
showToast("error", e.message);
|
||||
|
||||
@@ -44,11 +44,20 @@ export default class Subscriptions {
|
||||
);
|
||||
}
|
||||
|
||||
async transactions() {
|
||||
const token = await this._tokenManager.getAccessToken();
|
||||
if (!token) return;
|
||||
return await http.get(
|
||||
`${hosts.SUBSCRIPTIONS_HOST}/subscriptions/transactions`,
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
async updateUrl() {
|
||||
const token = await this._tokenManager.getAccessToken();
|
||||
if (!token) return;
|
||||
return await http.get(
|
||||
`${hosts.SUBSCRIPTIONS_HOST}/subscriptions/update_url`,
|
||||
`${hosts.SUBSCRIPTIONS_HOST}/subscriptions/update`,
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user