CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/billing/invoice.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { useState } from "../app-framework";
7
import { Row, Col } from "../antd-bootstrap";
8
import { Icon } from "../components";
9
import { open_popup_window } from "../misc/open-browser-tab";
10
import { capitalize, stripeDate } from "@cocalc/util/misc";
11
import { render_amount } from "./util";
12
import { InvoiceMap, InvoiceLineMap } from "./types";
13
14
interface Props {
15
invoice: InvoiceMap;
16
}
17
18
export const Invoice: React.FC<Props> = ({ invoice }) => {
19
const [hide_line_items, set_hide_line_items] = useState<boolean>(true);
20
21
function download(e): void {
22
e.preventDefault();
23
const url = invoice.get("hosted_invoice_url");
24
if (url) {
25
open_popup_window(url as string);
26
}
27
return;
28
}
29
30
function render_paid_status(): JSX.Element {
31
// The status of the invoice: one of draft, open, paid, uncollectible, or void
32
const status = capitalize(invoice.get("status") ?? "");
33
if (invoice.get("hosted_invoice_url")) {
34
return (
35
<a
36
style={status == "Open" ? { color: "red" } : undefined}
37
onClick={download}
38
>
39
{status == "Open" ? " Click here to pay..." : status}
40
</a>
41
);
42
} else {
43
return <span>{status}</span>;
44
}
45
}
46
47
function render_description(): JSX.Element {
48
const cnt = invoice.getIn(["lines", "total_count"]) ?? 0;
49
if (hide_line_items && cnt > 0) {
50
// This is much more useful as a summary than the totally generic description we usually have...
51
return (
52
<span>
53
{invoice.getIn(["lines", "data", 0, "description"])}
54
{cnt > 1 ? ", etc." : ""}
55
</span>
56
);
57
}
58
if (invoice.get("description")) {
59
return <span>{invoice.get("description")}</span>;
60
} else {
61
// This is what the description always is when it is non-empty, and it seems useful enough...
62
return <span>Thank you for using CoCalc by Sagemath, Inc.</span>;
63
}
64
}
65
66
function render_line_description(line: InvoiceLineMap): string[] {
67
const v: string[] = [];
68
if (line.get("quantity") > 1) {
69
v.push(`${line.get("quantity")} × `);
70
}
71
if (line.get("description") != null) {
72
v.push(line.get("description"));
73
}
74
if (line.get("plan") != null) {
75
v.push(line.getIn(["plan", "name"]));
76
v.push(` (start: ${stripeDate(line.getIn(["period", "start"]))})`);
77
}
78
return v;
79
}
80
81
function render_line_item(line: InvoiceLineMap, n): JSX.Element {
82
return (
83
<Row key={line.get("id")} style={{ borderBottom: "1px solid #aaa" }}>
84
<Col sm={1}>{n}.</Col>
85
<Col sm={9}>{render_line_description(line)}</Col>
86
<Col sm={2}>
87
{render_amount(line.get("amount"), invoice.get("currency"))}
88
</Col>
89
</Row>
90
);
91
}
92
93
function render_tax(): JSX.Element {
94
return (
95
<Row key="tax" style={{ borderBottom: "1px solid #aaa" }}>
96
<Col sm={1} />
97
<Col sm={9}>WA State Sales Tax ({invoice.get("tax_percent")}%)</Col>
98
<Col sm={2}>
99
{render_amount(invoice.get("tax"), invoice.get("currency"))}
100
</Col>
101
</Row>
102
);
103
}
104
105
function render_line_items(): undefined | JSX.Element | JSX.Element[] {
106
if (invoice.get("lines") == null) return;
107
if (hide_line_items) {
108
return (
109
<a
110
href=""
111
onClick={(e) => {
112
e.preventDefault();
113
set_hide_line_items(false);
114
}}
115
>
116
(show details)
117
</a>
118
);
119
} else {
120
const v: JSX.Element[] = [];
121
v.push(
122
<a
123
key="hide"
124
href=""
125
onClick={(e) => {
126
e.preventDefault();
127
set_hide_line_items(true);
128
}}
129
>
130
(hide details)
131
</a>
132
);
133
let n = 1;
134
for (const line of invoice.getIn(["lines", "data"], [] as any)) {
135
v.push(render_line_item(line, n));
136
n += 1;
137
}
138
if (invoice.get("tax")) {
139
v.push(render_tax());
140
}
141
return v;
142
}
143
}
144
145
const style: React.CSSProperties = {
146
borderBottom: "1px solid #999",
147
padding: hide_line_items ? "0" : "15px 0",
148
margin: "0",
149
};
150
return (
151
<Row style={style}>
152
<Col md={1}>
153
{render_amount(invoice.get("amount_due"), invoice.get("currency"))}
154
</Col>
155
<Col md={1}>{render_paid_status()}</Col>
156
<Col md={2}>{stripeDate(invoice.get("created"))}</Col>
157
<Col md={6}>
158
{render_description()} {render_line_items()}
159
</Col>
160
<Col md={2}>
161
<a onClick={download} href="">
162
<Icon name="external-link" />
163
{hide_line_items ? "" : " Download..."}
164
</a>
165
</Col>
166
</Row>
167
);
168
};
169
170