web: allow users to view their billing history

This commit is contained in:
Abdullah Atta
2023-05-01 12:56:22 +05:00
committed by Abdullah Atta
parent 7f873f240e
commit 2bd0f1a304
5 changed files with 178 additions and 9 deletions

View File

@@ -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":

View 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>
);
}

View File

@@ -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
};

View File

@@ -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);

View File

@@ -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
);
}