@base316/react 0.2.0 → 0.3.0
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/CHANGELOG.md +18 -0
- package/README.md +14 -0
- package/dist/index.cjs +225 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +230 -21
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +20 -5
- package/dist/src/PricingTable.d.ts.map +1 -1
- package/dist/src/SignIn.d.ts +10 -2
- package/dist/src/SignIn.d.ts.map +1 -1
- package/dist/src/SubscriptionManager.d.ts.map +1 -1
- package/dist/src/TeamMembers.d.ts.map +1 -1
- package/dist/src/TwoFactorManager.d.ts +14 -5
- package/dist/src/TwoFactorManager.d.ts.map +1 -1
- package/dist/src/appearance.d.ts +1 -1
- package/dist/src/appearance.d.ts.map +1 -1
- package/dist/src/manifest.d.ts.map +1 -1
- package/dist/src/provider.d.ts +13 -1
- package/dist/src/provider.d.ts.map +1 -1
- package/dist/src/usePlans.d.ts +8 -0
- package/dist/src/usePlans.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -150,6 +150,10 @@ var STYLESHEET = `
|
|
|
150
150
|
align-items: stretch;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/* Messages above the cards span the row rather than taking a card's column. */
|
|
154
|
+
.b316-plans-error,
|
|
155
|
+
.b316-plans-managed { grid-column: 1 / -1; }
|
|
156
|
+
|
|
153
157
|
.b316-plan {
|
|
154
158
|
display: flex;
|
|
155
159
|
flex-direction: column;
|
|
@@ -612,6 +616,7 @@ function Base316Provider({
|
|
|
612
616
|
token: controlledToken,
|
|
613
617
|
onTokenChange,
|
|
614
618
|
appearance: appearance2,
|
|
619
|
+
tenant,
|
|
615
620
|
className,
|
|
616
621
|
children
|
|
617
622
|
}) {
|
|
@@ -640,16 +645,20 @@ function Base316Provider({
|
|
|
640
645
|
}
|
|
641
646
|
setInternalToken(readStoredToken());
|
|
642
647
|
}, [controlled]);
|
|
648
|
+
const tenantKey = tenant ? "organizationId" in tenant ? `id:${tenant.organizationId}` : `ext:${tenant.organizationExternalId}` : null;
|
|
643
649
|
useEffect(() => {
|
|
644
650
|
let cancelled = false;
|
|
645
|
-
client.billing.
|
|
651
|
+
const request = tenantKey ? client.billing.tenantBranding(
|
|
652
|
+
tenantKey.startsWith("id:") ? { organizationId: tenantKey.slice(3) } : { organizationExternalId: tenantKey.slice(4) }
|
|
653
|
+
) : client.billing.branding();
|
|
654
|
+
request.then((result) => {
|
|
646
655
|
if (!cancelled) setBranding(result);
|
|
647
656
|
}).catch(() => {
|
|
648
657
|
});
|
|
649
658
|
return () => {
|
|
650
659
|
cancelled = true;
|
|
651
660
|
};
|
|
652
|
-
}, [client]);
|
|
661
|
+
}, [client, tenantKey]);
|
|
653
662
|
const setToken = useCallback(
|
|
654
663
|
(next) => {
|
|
655
664
|
if (controlled) {
|
|
@@ -941,13 +950,18 @@ function InvoiceView({ invoice, onClose }) {
|
|
|
941
950
|
}
|
|
942
951
|
|
|
943
952
|
// src/SignIn.tsx
|
|
944
|
-
import {
|
|
953
|
+
import {
|
|
954
|
+
isPasskeyAutofillAvailable,
|
|
955
|
+
isPasskeySupported,
|
|
956
|
+
needsSecondFactor
|
|
957
|
+
} from "@base316/sdk";
|
|
945
958
|
import { useEffect as useEffect3, useRef, useState as useState3 } from "react";
|
|
946
959
|
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
947
960
|
function SignIn({
|
|
948
961
|
onSignedIn,
|
|
949
962
|
passkeys = true,
|
|
950
963
|
magicLinkRedirectUrl,
|
|
964
|
+
phone = false,
|
|
951
965
|
className,
|
|
952
966
|
style
|
|
953
967
|
}) {
|
|
@@ -956,6 +970,11 @@ function SignIn({
|
|
|
956
970
|
const [password, setPassword] = useState3("");
|
|
957
971
|
const [code, setCode] = useState3("");
|
|
958
972
|
const [pendingChallenge, setPendingChallenge] = useState3(null);
|
|
973
|
+
const [pendingFactors, setPendingFactors] = useState3([]);
|
|
974
|
+
const [phoneNumber, setPhoneNumber] = useState3("");
|
|
975
|
+
const [phoneChallenge, setPhoneChallenge] = useState3(
|
|
976
|
+
null
|
|
977
|
+
);
|
|
959
978
|
const [error, setError] = useState3(null);
|
|
960
979
|
const [busy, setBusy] = useState3(false);
|
|
961
980
|
const [passkeyReady, setPasskeyReady] = useState3(false);
|
|
@@ -1062,9 +1081,30 @@ function SignIn({
|
|
|
1062
1081
|
onSignedIn?.();
|
|
1063
1082
|
return;
|
|
1064
1083
|
}
|
|
1084
|
+
if (mode === "phone") {
|
|
1085
|
+
if (!phoneChallenge) {
|
|
1086
|
+
const started = await client.auth.phone.start({ phone: phoneNumber });
|
|
1087
|
+
setPhoneChallenge({ token: started.challengeToken, hint: started.destinationHint });
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const signedIn = await client.auth.phone.verify({
|
|
1091
|
+
challengeToken: phoneChallenge.token,
|
|
1092
|
+
code
|
|
1093
|
+
});
|
|
1094
|
+
if (needsSecondFactor(signedIn)) {
|
|
1095
|
+
setPendingChallenge(signedIn.challengeToken);
|
|
1096
|
+
setPendingFactors(signedIn.factors);
|
|
1097
|
+
setCode("");
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
setToken(signedIn.token);
|
|
1101
|
+
onSignedIn?.();
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1065
1104
|
const result = await client.auth.signIn({ email, password });
|
|
1066
1105
|
if (needsSecondFactor(result)) {
|
|
1067
1106
|
setPendingChallenge(result.challengeToken);
|
|
1107
|
+
setPendingFactors(result.factors);
|
|
1068
1108
|
return;
|
|
1069
1109
|
}
|
|
1070
1110
|
setToken(result.token);
|
|
@@ -1072,13 +1112,20 @@ function SignIn({
|
|
|
1072
1112
|
} catch (cause) {
|
|
1073
1113
|
console.error("[base316] sign-in failed", cause);
|
|
1074
1114
|
setError(
|
|
1075
|
-
pendingChallenge ? "That code was not accepted." : "
|
|
1115
|
+
pendingChallenge ? "That code was not accepted." : mode === "phone" ? phoneChallenge ? "That code did not work. Check it, or ask for a new one." : (
|
|
1116
|
+
// Why a text could not be sent is worth saying — a number in the
|
|
1117
|
+
// wrong format, or too many requests — and the server words it
|
|
1118
|
+
// without saying anything about accounts.
|
|
1119
|
+
cause instanceof Error && cause.message ? cause.message : "We could not send a code. Try again."
|
|
1120
|
+
) : "That email and password did not match."
|
|
1076
1121
|
);
|
|
1077
1122
|
} finally {
|
|
1078
1123
|
setBusy(false);
|
|
1079
1124
|
}
|
|
1080
1125
|
}
|
|
1081
1126
|
const magicAvailable = Boolean(magicLinkRedirectUrl) && !pendingChallenge;
|
|
1127
|
+
const tabsShown = (magicAvailable || phone) && !pendingChallenge;
|
|
1128
|
+
const deliveredFactor = pendingFactors.find((factor) => factor.destinationHint);
|
|
1082
1129
|
if (linkSent) {
|
|
1083
1130
|
return /* @__PURE__ */ jsxs3("div", { className: cls("b316-card", className), style, children: [
|
|
1084
1131
|
/* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, marginBottom: 6 }, children: "Check your email" }),
|
|
@@ -1101,7 +1148,7 @@ function SignIn({
|
|
|
1101
1148
|
}
|
|
1102
1149
|
return /* @__PURE__ */ jsxs3("form", { onSubmit: submit, className: cls("b316-card", className), style, children: [
|
|
1103
1150
|
error ? /* @__PURE__ */ jsx4("div", { className: "b316-error", role: "alert", children: error }) : null,
|
|
1104
|
-
|
|
1151
|
+
tabsShown ? /* @__PURE__ */ jsxs3("div", { className: "b316-tabs", children: [
|
|
1105
1152
|
/* @__PURE__ */ jsx4(
|
|
1106
1153
|
"button",
|
|
1107
1154
|
{
|
|
@@ -1111,7 +1158,7 @@ function SignIn({
|
|
|
1111
1158
|
children: "Password"
|
|
1112
1159
|
}
|
|
1113
1160
|
),
|
|
1114
|
-
/* @__PURE__ */ jsx4(
|
|
1161
|
+
magicAvailable ? /* @__PURE__ */ jsx4(
|
|
1115
1162
|
"button",
|
|
1116
1163
|
{
|
|
1117
1164
|
type: "button",
|
|
@@ -1119,7 +1166,16 @@ function SignIn({
|
|
|
1119
1166
|
onClick: () => setMode("magic"),
|
|
1120
1167
|
children: "Magic link"
|
|
1121
1168
|
}
|
|
1122
|
-
)
|
|
1169
|
+
) : null,
|
|
1170
|
+
phone ? /* @__PURE__ */ jsx4(
|
|
1171
|
+
"button",
|
|
1172
|
+
{
|
|
1173
|
+
type: "button",
|
|
1174
|
+
className: cls("b316-tab", mode === "phone" ? "b316-tab-on" : void 0),
|
|
1175
|
+
onClick: () => setMode("phone"),
|
|
1176
|
+
children: "Text message"
|
|
1177
|
+
}
|
|
1178
|
+
) : null
|
|
1123
1179
|
] }) : null,
|
|
1124
1180
|
pendingChallenge ? /* @__PURE__ */ jsxs3("div", { className: "b316-field", children: [
|
|
1125
1181
|
/* @__PURE__ */ jsx4("label", { className: "b316-label", htmlFor: "b316-code", children: "Authentication code" }),
|
|
@@ -1135,7 +1191,55 @@ function SignIn({
|
|
|
1135
1191
|
required: true
|
|
1136
1192
|
}
|
|
1137
1193
|
),
|
|
1138
|
-
/* @__PURE__ */ jsx4("span", { className: "b316-muted", children: "From your authenticator app, or one of your recovery codes." })
|
|
1194
|
+
/* @__PURE__ */ jsx4("span", { className: "b316-muted", children: deliveredFactor ? `We sent a code to ${deliveredFactor.destinationHint}. Enter it, or one of your recovery codes.` : "From your authenticator app, or one of your recovery codes." })
|
|
1195
|
+
] }) : mode === "phone" ? phoneChallenge ? /* @__PURE__ */ jsxs3("div", { className: "b316-field", children: [
|
|
1196
|
+
/* @__PURE__ */ jsx4("label", { className: "b316-label", htmlFor: "b316-phone-code", children: "Code" }),
|
|
1197
|
+
/* @__PURE__ */ jsx4(
|
|
1198
|
+
"input",
|
|
1199
|
+
{
|
|
1200
|
+
id: "b316-phone-code",
|
|
1201
|
+
className: "b316-input",
|
|
1202
|
+
value: code,
|
|
1203
|
+
onChange: (event) => setCode(event.target.value),
|
|
1204
|
+
autoComplete: "one-time-code",
|
|
1205
|
+
inputMode: "numeric",
|
|
1206
|
+
required: true
|
|
1207
|
+
}
|
|
1208
|
+
),
|
|
1209
|
+
/* @__PURE__ */ jsxs3("span", { className: "b316-muted", children: [
|
|
1210
|
+
"We sent a code to ",
|
|
1211
|
+
phoneChallenge.hint,
|
|
1212
|
+
". It expires in ten minutes.",
|
|
1213
|
+
" ",
|
|
1214
|
+
/* @__PURE__ */ jsx4(
|
|
1215
|
+
"button",
|
|
1216
|
+
{
|
|
1217
|
+
type: "button",
|
|
1218
|
+
className: "b316-button-quiet",
|
|
1219
|
+
onClick: () => {
|
|
1220
|
+
setPhoneChallenge(null);
|
|
1221
|
+
setCode("");
|
|
1222
|
+
},
|
|
1223
|
+
children: "Use a different number"
|
|
1224
|
+
}
|
|
1225
|
+
)
|
|
1226
|
+
] })
|
|
1227
|
+
] }) : /* @__PURE__ */ jsxs3("div", { className: "b316-field", children: [
|
|
1228
|
+
/* @__PURE__ */ jsx4("label", { className: "b316-label", htmlFor: "b316-phone", children: "Mobile number" }),
|
|
1229
|
+
/* @__PURE__ */ jsx4(
|
|
1230
|
+
"input",
|
|
1231
|
+
{
|
|
1232
|
+
id: "b316-phone",
|
|
1233
|
+
className: "b316-input",
|
|
1234
|
+
type: "tel",
|
|
1235
|
+
value: phoneNumber,
|
|
1236
|
+
onChange: (event) => setPhoneNumber(event.target.value),
|
|
1237
|
+
autoComplete: "tel",
|
|
1238
|
+
placeholder: "+14155550123",
|
|
1239
|
+
required: true
|
|
1240
|
+
}
|
|
1241
|
+
),
|
|
1242
|
+
/* @__PURE__ */ jsx4("span", { className: "b316-muted", children: "Include the country code." })
|
|
1139
1243
|
] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1140
1244
|
/* @__PURE__ */ jsxs3("div", { className: "b316-field", children: [
|
|
1141
1245
|
/* @__PURE__ */ jsx4("label", { className: "b316-label", htmlFor: "b316-email", children: "Email" }),
|
|
@@ -1168,7 +1272,7 @@ function SignIn({
|
|
|
1168
1272
|
)
|
|
1169
1273
|
] }) : null
|
|
1170
1274
|
] }),
|
|
1171
|
-
/* @__PURE__ */ jsx4("button", { className: "b316-button", type: "submit", disabled: busy, children: busy ? "Working\u2026" : pendingChallenge ? "Verify" : mode === "magic" ? "Email me a link" : "Sign in" }),
|
|
1275
|
+
/* @__PURE__ */ jsx4("button", { className: "b316-button", type: "submit", disabled: busy, children: busy ? "Working\u2026" : pendingChallenge ? "Verify" : mode === "magic" ? "Email me a link" : mode === "phone" && !phoneChallenge ? "Text me a code" : "Sign in" }),
|
|
1172
1276
|
passkeyReady && !pendingChallenge ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1173
1277
|
/* @__PURE__ */ jsx4("div", { className: "b316-muted", style: { textAlign: "center", margin: "10px 0" }, children: "or" }),
|
|
1174
1278
|
/* @__PURE__ */ jsx4(
|
|
@@ -1783,6 +1887,7 @@ import { useState as useState10 } from "react";
|
|
|
1783
1887
|
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1784
1888
|
function TwoFactorManager({
|
|
1785
1889
|
enabled = false,
|
|
1890
|
+
allowSms = false,
|
|
1786
1891
|
onEnabled,
|
|
1787
1892
|
onDisabled,
|
|
1788
1893
|
className,
|
|
@@ -1792,6 +1897,7 @@ function TwoFactorManager({
|
|
|
1792
1897
|
const [stage, setStage] = useState10({ name: "idle" });
|
|
1793
1898
|
const [on, setOn] = useState10(enabled);
|
|
1794
1899
|
const [code, setCode] = useState10("");
|
|
1900
|
+
const [phone, setPhone] = useState10("");
|
|
1795
1901
|
const [error, setError] = useState10(null);
|
|
1796
1902
|
const [busy, setBusy] = useState10(false);
|
|
1797
1903
|
if (!token) {
|
|
@@ -1821,6 +1927,21 @@ function TwoFactorManager({
|
|
|
1821
1927
|
setBusy(false);
|
|
1822
1928
|
}
|
|
1823
1929
|
}
|
|
1930
|
+
async function beginSms() {
|
|
1931
|
+
setBusy(true);
|
|
1932
|
+
setError(null);
|
|
1933
|
+
try {
|
|
1934
|
+
const enrollment = await client.auth.mfa.enrollSms(
|
|
1935
|
+
token,
|
|
1936
|
+
phone.trim() ? { phone: phone.trim() } : {}
|
|
1937
|
+
);
|
|
1938
|
+
setStage({ name: "enrollingEmail", ...enrollment });
|
|
1939
|
+
} catch (cause) {
|
|
1940
|
+
setError(cause instanceof Error ? cause.message : "Could not send a code.");
|
|
1941
|
+
} finally {
|
|
1942
|
+
setBusy(false);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1824
1945
|
async function confirm() {
|
|
1825
1946
|
if (stage.name !== "enrolling" && stage.name !== "enrollingEmail") {
|
|
1826
1947
|
return;
|
|
@@ -1863,6 +1984,33 @@ function TwoFactorManager({
|
|
|
1863
1984
|
/* @__PURE__ */ jsx12("p", { children: "Save these recovery codes somewhere safe. Each one signs you in once if you lose your authenticator, and they are not shown again." }),
|
|
1864
1985
|
/* @__PURE__ */ jsx12("ul", { className: "b316-mfa-code-list", children: stage.recoveryCodes.map((recovery) => /* @__PURE__ */ jsx12("li", { children: /* @__PURE__ */ jsx12("code", { children: recovery }) }, recovery)) }),
|
|
1865
1986
|
/* @__PURE__ */ jsx12("button", { type: "button", className: "b316-button", onClick: () => setStage({ name: "idle" }), children: "I have saved them" })
|
|
1987
|
+
] }) : stage.name === "smsNumber" ? /* @__PURE__ */ jsxs11("div", { className: "b316-mfa-enrol", children: [
|
|
1988
|
+
/* @__PURE__ */ jsx12("p", { children: "We will text a code to this number now, and each time you sign in." }),
|
|
1989
|
+
/* @__PURE__ */ jsx12("label", { className: "b316-label", htmlFor: "b316-mfa-phone", children: "Mobile number" }),
|
|
1990
|
+
/* @__PURE__ */ jsx12(
|
|
1991
|
+
"input",
|
|
1992
|
+
{
|
|
1993
|
+
id: "b316-mfa-phone",
|
|
1994
|
+
className: "b316-input",
|
|
1995
|
+
type: "tel",
|
|
1996
|
+
autoComplete: "tel",
|
|
1997
|
+
placeholder: "+14155550123",
|
|
1998
|
+
value: phone,
|
|
1999
|
+
onChange: (event) => setPhone(event.target.value)
|
|
2000
|
+
}
|
|
2001
|
+
),
|
|
2002
|
+
/* @__PURE__ */ jsxs11("div", { className: "b316-mfa-actions", children: [
|
|
2003
|
+
/* @__PURE__ */ jsx12(
|
|
2004
|
+
"button",
|
|
2005
|
+
{
|
|
2006
|
+
type: "button",
|
|
2007
|
+
className: "b316-button-quiet",
|
|
2008
|
+
onClick: () => setStage({ name: "idle" }),
|
|
2009
|
+
children: "Cancel"
|
|
2010
|
+
}
|
|
2011
|
+
),
|
|
2012
|
+
/* @__PURE__ */ jsx12("button", { type: "button", className: "b316-button", disabled: busy, onClick: beginSms, children: busy ? "Sending\u2026" : "Send code" })
|
|
2013
|
+
] })
|
|
1866
2014
|
] }) : stage.name === "enrollingEmail" ? /* @__PURE__ */ jsxs11("div", { className: "b316-mfa-enrol", children: [
|
|
1867
2015
|
/* @__PURE__ */ jsxs11("p", { children: [
|
|
1868
2016
|
"We sent a code to ",
|
|
@@ -1964,7 +2112,17 @@ function TwoFactorManager({
|
|
|
1964
2112
|
onClick: beginEmail,
|
|
1965
2113
|
children: "Email me a code"
|
|
1966
2114
|
}
|
|
1967
|
-
)
|
|
2115
|
+
),
|
|
2116
|
+
allowSms ? /* @__PURE__ */ jsx12(
|
|
2117
|
+
"button",
|
|
2118
|
+
{
|
|
2119
|
+
type: "button",
|
|
2120
|
+
className: "b316-button-quiet",
|
|
2121
|
+
disabled: busy,
|
|
2122
|
+
onClick: () => setStage({ name: "smsNumber" }),
|
|
2123
|
+
children: "Text me a code"
|
|
2124
|
+
}
|
|
2125
|
+
) : null
|
|
1968
2126
|
] })
|
|
1969
2127
|
] })
|
|
1970
2128
|
] });
|
|
@@ -2296,6 +2454,16 @@ function TeamMembers({
|
|
|
2296
2454
|
}
|
|
2297
2455
|
return /* @__PURE__ */ jsxs15("div", { className: cls("b316-card b316-team", className), style, children: [
|
|
2298
2456
|
/* @__PURE__ */ jsx16("h3", { className: "b316-team-title", children: team?.name ?? "Team" }),
|
|
2457
|
+
team?.inherited ? (
|
|
2458
|
+
// Not a member of this team, so not in the list below and with nothing
|
|
2459
|
+
// to leave: access comes from the team that manages it, and ends there.
|
|
2460
|
+
/* @__PURE__ */ jsxs15("div", { className: "b316-muted", children: [
|
|
2461
|
+
"You have access through",
|
|
2462
|
+
" ",
|
|
2463
|
+
teams.find((candidate) => candidate.id === team.via)?.name ?? "the organization that manages this team",
|
|
2464
|
+
"."
|
|
2465
|
+
] })
|
|
2466
|
+
) : null,
|
|
2299
2467
|
error ? /* @__PURE__ */ jsx16("div", { className: "b316-error", role: "alert", children: error }) : null,
|
|
2300
2468
|
/* @__PURE__ */ jsx16("ul", { className: "b316-team-list", children: (members ?? []).map((member) => /* @__PURE__ */ jsxs15("li", { className: "b316-team-member", children: [
|
|
2301
2469
|
/* @__PURE__ */ jsxs15("div", { children: [
|
|
@@ -2652,6 +2820,7 @@ function usePlans(options = {}) {
|
|
|
2652
2820
|
const [busyPlanVersionId, setBusy] = useState18(null);
|
|
2653
2821
|
const [lastError, setLastError] = useState18(null);
|
|
2654
2822
|
const [ownPlanVersionId, setOwnPlanVersionId] = useState18(null);
|
|
2823
|
+
const [managedBy, setManagedBy] = useState18(null);
|
|
2655
2824
|
const [reloadCount, setReloadCount] = useState18(0);
|
|
2656
2825
|
const supplied = options.currentPlanVersionId;
|
|
2657
2826
|
const discover = supplied === void 0;
|
|
@@ -2672,13 +2841,16 @@ function usePlans(options = {}) {
|
|
|
2672
2841
|
};
|
|
2673
2842
|
}, [client, reloadCount]);
|
|
2674
2843
|
useEffect13(() => {
|
|
2675
|
-
if (!
|
|
2844
|
+
if (!token) {
|
|
2676
2845
|
return;
|
|
2677
2846
|
}
|
|
2678
2847
|
let cancelled = false;
|
|
2679
2848
|
client.billing.subscription(token).then((result) => {
|
|
2680
2849
|
if (!cancelled) {
|
|
2681
|
-
|
|
2850
|
+
setManagedBy(result.managedBy ?? null);
|
|
2851
|
+
if (discover) {
|
|
2852
|
+
setOwnPlanVersionId(result.subscription?.planVersion.id ?? null);
|
|
2853
|
+
}
|
|
2682
2854
|
}
|
|
2683
2855
|
}).catch(() => null);
|
|
2684
2856
|
return () => {
|
|
@@ -2692,6 +2864,11 @@ function usePlans(options = {}) {
|
|
|
2692
2864
|
const selectPlan = useCallback6(
|
|
2693
2865
|
async (plan) => {
|
|
2694
2866
|
setLastError(null);
|
|
2867
|
+
if (managedBy) {
|
|
2868
|
+
const reason = `Your plan is provided by ${managedBy.name ?? "the organization that manages your account"}.`;
|
|
2869
|
+
setLastError(reason);
|
|
2870
|
+
return { status: "failed", reason };
|
|
2871
|
+
}
|
|
2695
2872
|
if (!token) {
|
|
2696
2873
|
return { status: "needs-sign-in" };
|
|
2697
2874
|
}
|
|
@@ -2728,7 +2905,7 @@ function usePlans(options = {}) {
|
|
|
2728
2905
|
setBusy(null);
|
|
2729
2906
|
}
|
|
2730
2907
|
},
|
|
2731
|
-
[client, token, options.navigate]
|
|
2908
|
+
[client, token, options.navigate, managedBy]
|
|
2732
2909
|
);
|
|
2733
2910
|
return {
|
|
2734
2911
|
plans,
|
|
@@ -2738,6 +2915,7 @@ function usePlans(options = {}) {
|
|
|
2738
2915
|
busyPlanVersionId,
|
|
2739
2916
|
lastError,
|
|
2740
2917
|
signedIn: token !== null,
|
|
2918
|
+
managedBy,
|
|
2741
2919
|
selectPlan,
|
|
2742
2920
|
reload
|
|
2743
2921
|
};
|
|
@@ -2777,6 +2955,7 @@ function PricingTable({
|
|
|
2777
2955
|
busyPlanVersionId: busy,
|
|
2778
2956
|
lastError,
|
|
2779
2957
|
signedIn,
|
|
2958
|
+
managedBy,
|
|
2780
2959
|
selectPlan
|
|
2781
2960
|
} = usePlans({ currentPlanVersionId });
|
|
2782
2961
|
async function choose(plan) {
|
|
@@ -2802,7 +2981,16 @@ function PricingTable({
|
|
|
2802
2981
|
const anyDescription = plans.some((plan) => Boolean(plan.description));
|
|
2803
2982
|
const anyFeatured = plans.some((plan) => plan.key === featuredPlanKey);
|
|
2804
2983
|
return /* @__PURE__ */ jsxs18("div", { className: cls("b316-plans", className), style, children: [
|
|
2805
|
-
|
|
2984
|
+
managedBy ? (
|
|
2985
|
+
// A client company's plan comes from the firm that runs it. The table
|
|
2986
|
+
// still shows what is on offer, with the current plan marked, but
|
|
2987
|
+
// nothing here can be bought: it would grant this account nothing.
|
|
2988
|
+
/* @__PURE__ */ jsxs18("div", { className: "b316-plans-managed b316-muted", children: [
|
|
2989
|
+
"Your plan is provided by ",
|
|
2990
|
+
managedBy.name ?? "the organization that manages your account",
|
|
2991
|
+
". Ask them to change it."
|
|
2992
|
+
] })
|
|
2993
|
+
) : lastError ? /* @__PURE__ */ jsx19("div", { className: "b316-plans-error", role: "alert", children: lastError }) : null,
|
|
2806
2994
|
plans.map((plan) => {
|
|
2807
2995
|
const base = plan.prices.find((price) => price.featureId === null) ?? plan.prices[0];
|
|
2808
2996
|
const isCurrent = current === plan.planVersionId;
|
|
@@ -2831,7 +3019,7 @@ function PricingTable({
|
|
|
2831
3019
|
{
|
|
2832
3020
|
type: "button",
|
|
2833
3021
|
className: isCurrent ? "b316-button b316-button-current" : "b316-button",
|
|
2834
|
-
disabled: isCurrent || busy !== null || !signedIn && !signInUrl && !onSelectPlan,
|
|
3022
|
+
disabled: isCurrent || managedBy !== null || busy !== null || !signedIn && !signInUrl && !onSelectPlan,
|
|
2835
3023
|
onClick: () => {
|
|
2836
3024
|
void choose(plan);
|
|
2837
3025
|
},
|
|
@@ -4174,6 +4362,7 @@ function SubscriptionManager({
|
|
|
4174
4362
|
const { client, token, loading: sessionLoading } = useBase316();
|
|
4175
4363
|
const [subscription, setSubscription] = useState25(null);
|
|
4176
4364
|
const [loading, setLoading] = useState25(true);
|
|
4365
|
+
const [managedBy, setManagedBy] = useState25(null);
|
|
4177
4366
|
const [busy, setBusy] = useState25(false);
|
|
4178
4367
|
const [error, setError] = useState25(null);
|
|
4179
4368
|
const load = useCallback8(async () => {
|
|
@@ -4185,6 +4374,7 @@ function SubscriptionManager({
|
|
|
4185
4374
|
try {
|
|
4186
4375
|
const result = await client.billing.subscription(token);
|
|
4187
4376
|
setSubscription(result.subscription);
|
|
4377
|
+
setManagedBy(result.managedBy ?? null);
|
|
4188
4378
|
} catch (cause) {
|
|
4189
4379
|
console.error("[base316] could not load the subscription", cause);
|
|
4190
4380
|
setError("Could not load your subscription.");
|
|
@@ -4216,7 +4406,11 @@ function SubscriptionManager({
|
|
|
4216
4406
|
" \xB7 ",
|
|
4217
4407
|
subscription.cancelAtPeriodEnd ? `Ends ${renews}` : `Renews ${renews}`
|
|
4218
4408
|
] }),
|
|
4219
|
-
|
|
4409
|
+
managedBy ? /* @__PURE__ */ jsxs26("p", { className: "b316-muted", children: [
|
|
4410
|
+
"Provided by ",
|
|
4411
|
+
managedBy.name ?? "the organization that manages your account",
|
|
4412
|
+
". Ask them to change or cancel it."
|
|
4413
|
+
] }) : subscription.cancelAtPeriodEnd ? /* @__PURE__ */ jsxs26("p", { className: "b316-muted", children: [
|
|
4220
4414
|
"This subscription will not renew. You keep access until ",
|
|
4221
4415
|
renews,
|
|
4222
4416
|
"."
|
|
@@ -4553,6 +4747,11 @@ var MANIFEST = [
|
|
|
4553
4747
|
summary: "Colours, radius and font as tokens. Restyles everything at once.",
|
|
4554
4748
|
required: false
|
|
4555
4749
|
},
|
|
4750
|
+
{
|
|
4751
|
+
name: "tenant",
|
|
4752
|
+
summary: "White-label everything inside for one tenant \u2014 `{ organizationId }` or `{ organizationExternalId }`. A firm's name, logo and colours on the screens it runs its clients on; its own branding, then its parent's, then the environment's. Change it when the user switches team.",
|
|
4753
|
+
required: false
|
|
4754
|
+
},
|
|
4556
4755
|
...appearance
|
|
4557
4756
|
],
|
|
4558
4757
|
seeAlso: ["useBase316", "useSession"]
|
|
@@ -4578,7 +4777,7 @@ var MANIFEST = [
|
|
|
4578
4777
|
{
|
|
4579
4778
|
name: "SignIn",
|
|
4580
4779
|
kind: "component",
|
|
4581
|
-
summary: "Password, passkey
|
|
4780
|
+
summary: "Password, passkey, magic-link and text-message sign-in in one form.",
|
|
4582
4781
|
useWhen: "The application renders its own sign-in rather than redirecting to the hosted page.",
|
|
4583
4782
|
requires: [],
|
|
4584
4783
|
props: [
|
|
@@ -4593,6 +4792,11 @@ var MANIFEST = [
|
|
|
4593
4792
|
summary: "Where an emailed link lands, which must be registered on the environment. Without it, magic link is not offered at all.",
|
|
4594
4793
|
required: false
|
|
4595
4794
|
},
|
|
4795
|
+
{
|
|
4796
|
+
name: "phone",
|
|
4797
|
+
summary: "Offer signing in with a code sent by text message. Needs text messages switched on for the environment, and its own Twilio account in production.",
|
|
4798
|
+
required: false
|
|
4799
|
+
},
|
|
4596
4800
|
...appearance
|
|
4597
4801
|
],
|
|
4598
4802
|
seeAlso: ["SignUp", "ForgotPassword"]
|
|
@@ -4726,8 +4930,8 @@ var MANIFEST = [
|
|
|
4726
4930
|
{
|
|
4727
4931
|
name: "TwoFactorManager",
|
|
4728
4932
|
kind: "component",
|
|
4729
|
-
summary: "Turn two-factor authentication on or off, by authenticator app
|
|
4730
|
-
useWhen: "A security settings page.
|
|
4933
|
+
summary: "Turn two-factor authentication on or off, by authenticator app, a code to the inbox or a code by text message.",
|
|
4934
|
+
useWhen: "A security settings page. The app is strongest; the emailed or texted code is the one people who will not install an app actually use.",
|
|
4731
4935
|
requires: ["session"],
|
|
4732
4936
|
props: [
|
|
4733
4937
|
{
|
|
@@ -4735,6 +4939,11 @@ var MANIFEST = [
|
|
|
4735
4939
|
summary: "Whether a factor already exists, which decides which side is shown.",
|
|
4736
4940
|
required: false
|
|
4737
4941
|
},
|
|
4942
|
+
{
|
|
4943
|
+
name: "allowSms",
|
|
4944
|
+
summary: "Offer a code by text message. Off by default: it only works once the environment can send one.",
|
|
4945
|
+
required: false
|
|
4946
|
+
},
|
|
4738
4947
|
{ name: "onEnabled", summary: "Called once a factor is confirmed.", required: false },
|
|
4739
4948
|
{ name: "onDisabled", summary: "Called once it is removed.", required: false },
|
|
4740
4949
|
...appearance
|
|
@@ -4755,7 +4964,7 @@ var MANIFEST = [
|
|
|
4755
4964
|
{
|
|
4756
4965
|
name: "Branding",
|
|
4757
4966
|
kind: "component",
|
|
4758
|
-
summary: "The
|
|
4967
|
+
summary: "The logo, name and tagline \u2014 the environment's, or the tenant's when the provider is given a `tenant`.",
|
|
4759
4968
|
useWhen: "Above a sign-in form, so your pages match the hosted ones.",
|
|
4760
4969
|
requires: [],
|
|
4761
4970
|
props: [
|
|
@@ -4767,7 +4976,7 @@ var MANIFEST = [
|
|
|
4767
4976
|
{
|
|
4768
4977
|
name: "TeamMembers",
|
|
4769
4978
|
kind: "component",
|
|
4770
|
-
summary: "Who is in a team, who has been invited, and the way in and out.",
|
|
4979
|
+
summary: "Who is in a team, who has been invited, and the way in and out. A team reached only through the organization that manages it says so, and offers nothing to leave.",
|
|
4771
4980
|
useWhen: "A Team or Members settings page. One component covers the list, the pending invitations, the invite box and leaving.",
|
|
4772
4981
|
requires: ["session"],
|
|
4773
4982
|
props: [
|