@meterbox/customer-portal 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/package.json +2 -2
- package/src/CancelSubscription.js +8 -6
- package/src/CustomerPortal.js +70 -20
- package/src/pages/AccountPage.js +4 -4
package/README.md
CHANGED
|
@@ -35,6 +35,13 @@ function handlePlanSelect(plan, billingInterval) {
|
|
|
35
35
|
/>
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
## Branding
|
|
39
|
+
|
|
40
|
+
The portal takes your branding from Settings → Branding
|
|
41
|
+
(`GET /v1/customer-portal/:id/branding`): your logo and company name in its
|
|
42
|
+
header, and your colors, font and buttons on every page. Pass `branding` to
|
|
43
|
+
override it.
|
|
44
|
+
|
|
38
45
|
## Pages
|
|
39
46
|
|
|
40
47
|
- **Account**: name, email, current plan, status (`GET /v1/customer-portal/:id/me`),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meterbox/customer-portal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Drop-in multi-page React customer portal for Meterbox: Account, Plans, Credits. Built on @meterbox/react.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://meterbox.ai",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@meterbox/react": "0.1.
|
|
20
|
+
"@meterbox/react": "0.1.5"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"react": ">=17",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const React = require("react");
|
|
9
|
+
const { theme } = require("@meterbox/react");
|
|
9
10
|
|
|
10
11
|
const REASONS = {
|
|
11
12
|
too_expensive: "It's too expensive",
|
|
@@ -17,12 +18,12 @@ const REASONS = {
|
|
|
17
18
|
|
|
18
19
|
const styles = {
|
|
19
20
|
box: {
|
|
20
|
-
fontFamily:
|
|
21
|
-
color:
|
|
21
|
+
fontFamily: theme.font,
|
|
22
|
+
color: theme.text,
|
|
22
23
|
border: "1px solid #e5e7eb",
|
|
23
24
|
borderRadius: 12,
|
|
24
25
|
padding: 16,
|
|
25
|
-
background:
|
|
26
|
+
background: theme.card,
|
|
26
27
|
display: "grid",
|
|
27
28
|
gap: 10,
|
|
28
29
|
maxWidth: 480,
|
|
@@ -31,13 +32,14 @@ const styles = {
|
|
|
31
32
|
row: { display: "flex", gap: 8, flexWrap: "wrap" },
|
|
32
33
|
button: {
|
|
33
34
|
padding: "6px 12px",
|
|
34
|
-
borderRadius: 8,
|
|
35
|
+
borderRadius: theme.radius(8),
|
|
35
36
|
border: "1px solid #d1d5db",
|
|
36
|
-
background:
|
|
37
|
+
background: theme.card,
|
|
38
|
+
color: theme.text,
|
|
37
39
|
cursor: "pointer",
|
|
38
40
|
fontSize: 13,
|
|
39
41
|
},
|
|
40
|
-
primary:
|
|
42
|
+
primary: theme.button,
|
|
41
43
|
danger: { color: "#b91c1c", borderColor: "#fecaca" },
|
|
42
44
|
offer: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "grid", gap: 6 },
|
|
43
45
|
error: { color: "#dc2626" },
|
package/src/CustomerPortal.js
CHANGED
|
@@ -12,14 +12,20 @@
|
|
|
12
12
|
* threshold={10} // (optional) low-balance trigger
|
|
13
13
|
* currency="USD" // (optional)
|
|
14
14
|
* granularity="day" // (optional)
|
|
15
|
+
* branding={{ accent_color }} // (optional) instead of the tenant's
|
|
15
16
|
* />
|
|
16
17
|
*
|
|
18
|
+
* Branded with the tenant's branding (Settings → Branding, fetched from
|
|
19
|
+
* GET /v1/customer-portal/:id/branding): logo and company name in the
|
|
20
|
+
* header, and every page and component themed through <BrandingProvider>.
|
|
21
|
+
*
|
|
17
22
|
* No router dependency — page state is internal. Hosts that want
|
|
18
23
|
* browser-history nav can drive `initialPage` from their own router and
|
|
19
24
|
* observe `onPageChange`.
|
|
20
25
|
*/
|
|
21
26
|
|
|
22
27
|
const React = require("react");
|
|
28
|
+
const { BrandingProvider, logoUrl, theme } = require("@meterbox/react");
|
|
23
29
|
const AccountPage = require("./pages/AccountPage");
|
|
24
30
|
const PlansPage = require("./pages/PlansPage");
|
|
25
31
|
const CreditsPage = require("./pages/CreditsPage");
|
|
@@ -29,8 +35,8 @@ const LABELS = { account: "Account", plans: "Plans", credits: "Credits" };
|
|
|
29
35
|
|
|
30
36
|
const styles = {
|
|
31
37
|
shell: {
|
|
32
|
-
fontFamily:
|
|
33
|
-
color:
|
|
38
|
+
fontFamily: theme.font,
|
|
39
|
+
color: theme.text,
|
|
34
40
|
display: "grid",
|
|
35
41
|
gap: 16,
|
|
36
42
|
maxWidth: 760,
|
|
@@ -52,11 +58,26 @@ const styles = {
|
|
|
52
58
|
color: "#374151",
|
|
53
59
|
},
|
|
54
60
|
tabActive: {
|
|
55
|
-
color:
|
|
56
|
-
borderBottom:
|
|
61
|
+
color: theme.accent,
|
|
62
|
+
borderBottom: `2px solid ${theme.accent}`,
|
|
57
63
|
},
|
|
64
|
+
header: { display: "flex", alignItems: "center", gap: 10 },
|
|
65
|
+
logo: { maxHeight: 32, maxWidth: 160 },
|
|
66
|
+
company: { fontSize: 16, fontWeight: 700 },
|
|
58
67
|
};
|
|
59
68
|
|
|
69
|
+
// The tenant's branding and company name; none (unbranded) if it can't load.
|
|
70
|
+
async function fetchBranding({ baseUrl, customerId, customerToken, fetchImpl }) {
|
|
71
|
+
const url = `${String(baseUrl).replace(/\/+$/, "")}/v1/customer-portal/${encodeURIComponent(
|
|
72
|
+
customerId,
|
|
73
|
+
)}/branding`;
|
|
74
|
+
const res = await fetchImpl(url, {
|
|
75
|
+
method: "GET",
|
|
76
|
+
headers: { Authorization: `Bearer ${customerToken}` },
|
|
77
|
+
});
|
|
78
|
+
return res.ok ? res.json() : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
function CustomerPortal({
|
|
61
82
|
baseUrl,
|
|
62
83
|
customerId,
|
|
@@ -68,10 +89,27 @@ function CustomerPortal({
|
|
|
68
89
|
threshold,
|
|
69
90
|
currency,
|
|
70
91
|
granularity,
|
|
92
|
+
branding,
|
|
71
93
|
fetch: fetchImpl,
|
|
72
94
|
}) {
|
|
73
95
|
const initial = PAGES.includes(initialPage) ? initialPage : "account";
|
|
74
96
|
const [page, setPage] = React.useState(initial);
|
|
97
|
+
const [tenant, setTenant] = React.useState(null);
|
|
98
|
+
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
let alive = true;
|
|
101
|
+
fetchBranding({ baseUrl, customerId, customerToken, fetchImpl: fetchImpl || globalThis.fetch })
|
|
102
|
+
.then((body) => {
|
|
103
|
+
if (alive) setTenant(body);
|
|
104
|
+
})
|
|
105
|
+
.catch(() => {});
|
|
106
|
+
return () => {
|
|
107
|
+
alive = false;
|
|
108
|
+
};
|
|
109
|
+
}, [baseUrl, customerId, customerToken, fetchImpl]);
|
|
110
|
+
const brand = branding || tenant?.branding || null;
|
|
111
|
+
const logo = logoUrl(brand);
|
|
112
|
+
const companyName = tenant?.company_name || null;
|
|
75
113
|
|
|
76
114
|
// Honor external nav changes via initialPage prop.
|
|
77
115
|
React.useEffect(() => {
|
|
@@ -100,27 +138,39 @@ function CustomerPortal({
|
|
|
100
138
|
}
|
|
101
139
|
|
|
102
140
|
return React.createElement(
|
|
103
|
-
|
|
104
|
-
{
|
|
141
|
+
BrandingProvider,
|
|
142
|
+
{ branding: brand },
|
|
105
143
|
React.createElement(
|
|
106
|
-
"
|
|
107
|
-
{
|
|
108
|
-
|
|
144
|
+
"div",
|
|
145
|
+
{ className: "meterbox-customer-portal", style: styles.shell },
|
|
146
|
+
(logo || companyName) &&
|
|
109
147
|
React.createElement(
|
|
110
|
-
"
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
148
|
+
"header",
|
|
149
|
+
{ style: styles.header },
|
|
150
|
+
logo &&
|
|
151
|
+
React.createElement("img", { src: logo, alt: companyName || "", style: styles.logo }),
|
|
152
|
+
companyName && React.createElement("span", { style: styles.company }, companyName),
|
|
153
|
+
),
|
|
154
|
+
React.createElement(
|
|
155
|
+
"nav",
|
|
156
|
+
{ style: styles.nav, role: "tablist" },
|
|
157
|
+
PAGES.map((p) =>
|
|
158
|
+
React.createElement(
|
|
159
|
+
"button",
|
|
160
|
+
{
|
|
161
|
+
key: p,
|
|
162
|
+
type: "button",
|
|
163
|
+
role: "tab",
|
|
164
|
+
"aria-selected": page === p,
|
|
165
|
+
style: page === p ? { ...styles.tab, ...styles.tabActive } : styles.tab,
|
|
166
|
+
onClick: () => go(p),
|
|
167
|
+
},
|
|
168
|
+
LABELS[p],
|
|
169
|
+
),
|
|
120
170
|
),
|
|
121
171
|
),
|
|
172
|
+
body,
|
|
122
173
|
),
|
|
123
|
-
body,
|
|
124
174
|
);
|
|
125
175
|
}
|
|
126
176
|
|
package/src/pages/AccountPage.js
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const React = require("react");
|
|
8
|
-
const { InvoiceHistory } = require("@meterbox/react");
|
|
8
|
+
const { InvoiceHistory, theme } = require("@meterbox/react");
|
|
9
9
|
const CancelSubscription = require("../CancelSubscription");
|
|
10
10
|
|
|
11
11
|
const styles = {
|
|
12
12
|
card: {
|
|
13
|
-
fontFamily:
|
|
14
|
-
color:
|
|
13
|
+
fontFamily: theme.font,
|
|
14
|
+
color: theme.text,
|
|
15
15
|
border: "1px solid #e5e7eb",
|
|
16
16
|
borderRadius: 12,
|
|
17
17
|
padding: 20,
|
|
18
|
-
background:
|
|
18
|
+
background: theme.card,
|
|
19
19
|
display: "grid",
|
|
20
20
|
gap: 12,
|
|
21
21
|
maxWidth: 480,
|