@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @base316/react
|
|
2
2
|
|
|
3
|
+
## 0.3.0 — 2026-10-03
|
|
4
|
+
|
|
5
|
+
Requires `@base316/sdk` 0.3.0.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **White-label for a tenant**: `Base316Provider` takes `tenant` —
|
|
10
|
+
`{ organizationId }` or `{ organizationExternalId }` — and every component
|
|
11
|
+
under it shows that tenant's branding (its own, then its parent's, then the
|
|
12
|
+
environment's). Changing it restyles without a reload; an inline object does
|
|
13
|
+
not refetch.
|
|
14
|
+
- `TeamMembers` says when a team is reached through the organization that
|
|
15
|
+
manages it rather than by membership.
|
|
16
|
+
- `SubscriptionManager` and `PricingTable` know when an account's plan is
|
|
17
|
+
provided by the organization that manages it: the plan is shown with the
|
|
18
|
+
provider named, and nothing can be bought, changed or cancelled.
|
|
19
|
+
`usePlans()` returns `managedBy`.
|
|
20
|
+
|
|
3
21
|
## 0.2.0
|
|
4
22
|
|
|
5
23
|
The first release since `0.1.0` on 13 September. The package shipped **8
|
package/README.md
CHANGED
|
@@ -79,6 +79,20 @@ inherits your application's styles automatically — silently absorbing whatever
|
|
|
79
79
|
`body { font }` and `button { }` rules happen to exist produces components that
|
|
80
80
|
look broken in ways neither side can predict.
|
|
81
81
|
|
|
82
|
+
## White-label for a tenant
|
|
83
|
+
|
|
84
|
+
When one business runs your product for others — a firm and its clients — show
|
|
85
|
+
the firm's brand on the screens its clients see:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<Base316Provider publishableKey={key} tenant={{ organizationExternalId: firm.id }}>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The tenant's own branding, then its parent's, then your environment's, field
|
|
92
|
+
by field. Change `tenant` when the person switches team and every component
|
|
93
|
+
restyles. `TeamMembers` says so when a team is reached through the firm rather
|
|
94
|
+
than by membership.
|
|
95
|
+
|
|
82
96
|
## Reading the session
|
|
83
97
|
|
|
84
98
|
```tsx
|
package/dist/index.cjs
CHANGED
|
@@ -204,6 +204,10 @@ var STYLESHEET = `
|
|
|
204
204
|
align-items: stretch;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/* Messages above the cards span the row rather than taking a card's column. */
|
|
208
|
+
.b316-plans-error,
|
|
209
|
+
.b316-plans-managed { grid-column: 1 / -1; }
|
|
210
|
+
|
|
207
211
|
.b316-plan {
|
|
208
212
|
display: flex;
|
|
209
213
|
flex-direction: column;
|
|
@@ -666,6 +670,7 @@ function Base316Provider({
|
|
|
666
670
|
token: controlledToken,
|
|
667
671
|
onTokenChange,
|
|
668
672
|
appearance: appearance2,
|
|
673
|
+
tenant,
|
|
669
674
|
className,
|
|
670
675
|
children
|
|
671
676
|
}) {
|
|
@@ -694,16 +699,20 @@ function Base316Provider({
|
|
|
694
699
|
}
|
|
695
700
|
setInternalToken(readStoredToken());
|
|
696
701
|
}, [controlled]);
|
|
702
|
+
const tenantKey = tenant ? "organizationId" in tenant ? `id:${tenant.organizationId}` : `ext:${tenant.organizationExternalId}` : null;
|
|
697
703
|
(0, import_react.useEffect)(() => {
|
|
698
704
|
let cancelled = false;
|
|
699
|
-
client.billing.
|
|
705
|
+
const request = tenantKey ? client.billing.tenantBranding(
|
|
706
|
+
tenantKey.startsWith("id:") ? { organizationId: tenantKey.slice(3) } : { organizationExternalId: tenantKey.slice(4) }
|
|
707
|
+
) : client.billing.branding();
|
|
708
|
+
request.then((result) => {
|
|
700
709
|
if (!cancelled) setBranding(result);
|
|
701
710
|
}).catch(() => {
|
|
702
711
|
});
|
|
703
712
|
return () => {
|
|
704
713
|
cancelled = true;
|
|
705
714
|
};
|
|
706
|
-
}, [client]);
|
|
715
|
+
}, [client, tenantKey]);
|
|
707
716
|
const setToken = (0, import_react.useCallback)(
|
|
708
717
|
(next) => {
|
|
709
718
|
if (controlled) {
|
|
@@ -1002,6 +1011,7 @@ function SignIn({
|
|
|
1002
1011
|
onSignedIn,
|
|
1003
1012
|
passkeys = true,
|
|
1004
1013
|
magicLinkRedirectUrl,
|
|
1014
|
+
phone = false,
|
|
1005
1015
|
className,
|
|
1006
1016
|
style
|
|
1007
1017
|
}) {
|
|
@@ -1010,6 +1020,11 @@ function SignIn({
|
|
|
1010
1020
|
const [password, setPassword] = (0, import_react3.useState)("");
|
|
1011
1021
|
const [code, setCode] = (0, import_react3.useState)("");
|
|
1012
1022
|
const [pendingChallenge, setPendingChallenge] = (0, import_react3.useState)(null);
|
|
1023
|
+
const [pendingFactors, setPendingFactors] = (0, import_react3.useState)([]);
|
|
1024
|
+
const [phoneNumber, setPhoneNumber] = (0, import_react3.useState)("");
|
|
1025
|
+
const [phoneChallenge, setPhoneChallenge] = (0, import_react3.useState)(
|
|
1026
|
+
null
|
|
1027
|
+
);
|
|
1013
1028
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
1014
1029
|
const [busy, setBusy] = (0, import_react3.useState)(false);
|
|
1015
1030
|
const [passkeyReady, setPasskeyReady] = (0, import_react3.useState)(false);
|
|
@@ -1116,9 +1131,30 @@ function SignIn({
|
|
|
1116
1131
|
onSignedIn?.();
|
|
1117
1132
|
return;
|
|
1118
1133
|
}
|
|
1134
|
+
if (mode === "phone") {
|
|
1135
|
+
if (!phoneChallenge) {
|
|
1136
|
+
const started = await client.auth.phone.start({ phone: phoneNumber });
|
|
1137
|
+
setPhoneChallenge({ token: started.challengeToken, hint: started.destinationHint });
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const signedIn = await client.auth.phone.verify({
|
|
1141
|
+
challengeToken: phoneChallenge.token,
|
|
1142
|
+
code
|
|
1143
|
+
});
|
|
1144
|
+
if ((0, import_sdk2.needsSecondFactor)(signedIn)) {
|
|
1145
|
+
setPendingChallenge(signedIn.challengeToken);
|
|
1146
|
+
setPendingFactors(signedIn.factors);
|
|
1147
|
+
setCode("");
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
setToken(signedIn.token);
|
|
1151
|
+
onSignedIn?.();
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1119
1154
|
const result = await client.auth.signIn({ email, password });
|
|
1120
1155
|
if ((0, import_sdk2.needsSecondFactor)(result)) {
|
|
1121
1156
|
setPendingChallenge(result.challengeToken);
|
|
1157
|
+
setPendingFactors(result.factors);
|
|
1122
1158
|
return;
|
|
1123
1159
|
}
|
|
1124
1160
|
setToken(result.token);
|
|
@@ -1126,13 +1162,20 @@ function SignIn({
|
|
|
1126
1162
|
} catch (cause) {
|
|
1127
1163
|
console.error("[base316] sign-in failed", cause);
|
|
1128
1164
|
setError(
|
|
1129
|
-
pendingChallenge ? "That code was not accepted." : "
|
|
1165
|
+
pendingChallenge ? "That code was not accepted." : mode === "phone" ? phoneChallenge ? "That code did not work. Check it, or ask for a new one." : (
|
|
1166
|
+
// Why a text could not be sent is worth saying — a number in the
|
|
1167
|
+
// wrong format, or too many requests — and the server words it
|
|
1168
|
+
// without saying anything about accounts.
|
|
1169
|
+
cause instanceof Error && cause.message ? cause.message : "We could not send a code. Try again."
|
|
1170
|
+
) : "That email and password did not match."
|
|
1130
1171
|
);
|
|
1131
1172
|
} finally {
|
|
1132
1173
|
setBusy(false);
|
|
1133
1174
|
}
|
|
1134
1175
|
}
|
|
1135
1176
|
const magicAvailable = Boolean(magicLinkRedirectUrl) && !pendingChallenge;
|
|
1177
|
+
const tabsShown = (magicAvailable || phone) && !pendingChallenge;
|
|
1178
|
+
const deliveredFactor = pendingFactors.find((factor) => factor.destinationHint);
|
|
1136
1179
|
if (linkSent) {
|
|
1137
1180
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cls("b316-card", className), style, children: [
|
|
1138
1181
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontWeight: 600, marginBottom: 6 }, children: "Check your email" }),
|
|
@@ -1155,7 +1198,7 @@ function SignIn({
|
|
|
1155
1198
|
}
|
|
1156
1199
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { onSubmit: submit, className: cls("b316-card", className), style, children: [
|
|
1157
1200
|
error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "b316-error", role: "alert", children: error }) : null,
|
|
1158
|
-
|
|
1201
|
+
tabsShown ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "b316-tabs", children: [
|
|
1159
1202
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1160
1203
|
"button",
|
|
1161
1204
|
{
|
|
@@ -1165,7 +1208,7 @@ function SignIn({
|
|
|
1165
1208
|
children: "Password"
|
|
1166
1209
|
}
|
|
1167
1210
|
),
|
|
1168
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1211
|
+
magicAvailable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1169
1212
|
"button",
|
|
1170
1213
|
{
|
|
1171
1214
|
type: "button",
|
|
@@ -1173,7 +1216,16 @@ function SignIn({
|
|
|
1173
1216
|
onClick: () => setMode("magic"),
|
|
1174
1217
|
children: "Magic link"
|
|
1175
1218
|
}
|
|
1176
|
-
)
|
|
1219
|
+
) : null,
|
|
1220
|
+
phone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1221
|
+
"button",
|
|
1222
|
+
{
|
|
1223
|
+
type: "button",
|
|
1224
|
+
className: cls("b316-tab", mode === "phone" ? "b316-tab-on" : void 0),
|
|
1225
|
+
onClick: () => setMode("phone"),
|
|
1226
|
+
children: "Text message"
|
|
1227
|
+
}
|
|
1228
|
+
) : null
|
|
1177
1229
|
] }) : null,
|
|
1178
1230
|
pendingChallenge ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "b316-field", children: [
|
|
1179
1231
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "b316-label", htmlFor: "b316-code", children: "Authentication code" }),
|
|
@@ -1189,7 +1241,55 @@ function SignIn({
|
|
|
1189
1241
|
required: true
|
|
1190
1242
|
}
|
|
1191
1243
|
),
|
|
1192
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "b316-muted", children: "From your authenticator app, or one of your recovery codes." })
|
|
1244
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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." })
|
|
1245
|
+
] }) : mode === "phone" ? phoneChallenge ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "b316-field", children: [
|
|
1246
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "b316-label", htmlFor: "b316-phone-code", children: "Code" }),
|
|
1247
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1248
|
+
"input",
|
|
1249
|
+
{
|
|
1250
|
+
id: "b316-phone-code",
|
|
1251
|
+
className: "b316-input",
|
|
1252
|
+
value: code,
|
|
1253
|
+
onChange: (event) => setCode(event.target.value),
|
|
1254
|
+
autoComplete: "one-time-code",
|
|
1255
|
+
inputMode: "numeric",
|
|
1256
|
+
required: true
|
|
1257
|
+
}
|
|
1258
|
+
),
|
|
1259
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "b316-muted", children: [
|
|
1260
|
+
"We sent a code to ",
|
|
1261
|
+
phoneChallenge.hint,
|
|
1262
|
+
". It expires in ten minutes.",
|
|
1263
|
+
" ",
|
|
1264
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1265
|
+
"button",
|
|
1266
|
+
{
|
|
1267
|
+
type: "button",
|
|
1268
|
+
className: "b316-button-quiet",
|
|
1269
|
+
onClick: () => {
|
|
1270
|
+
setPhoneChallenge(null);
|
|
1271
|
+
setCode("");
|
|
1272
|
+
},
|
|
1273
|
+
children: "Use a different number"
|
|
1274
|
+
}
|
|
1275
|
+
)
|
|
1276
|
+
] })
|
|
1277
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "b316-field", children: [
|
|
1278
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "b316-label", htmlFor: "b316-phone", children: "Mobile number" }),
|
|
1279
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1280
|
+
"input",
|
|
1281
|
+
{
|
|
1282
|
+
id: "b316-phone",
|
|
1283
|
+
className: "b316-input",
|
|
1284
|
+
type: "tel",
|
|
1285
|
+
value: phoneNumber,
|
|
1286
|
+
onChange: (event) => setPhoneNumber(event.target.value),
|
|
1287
|
+
autoComplete: "tel",
|
|
1288
|
+
placeholder: "+14155550123",
|
|
1289
|
+
required: true
|
|
1290
|
+
}
|
|
1291
|
+
),
|
|
1292
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "b316-muted", children: "Include the country code." })
|
|
1193
1293
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1194
1294
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "b316-field", children: [
|
|
1195
1295
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "b316-label", htmlFor: "b316-email", children: "Email" }),
|
|
@@ -1222,7 +1322,7 @@ function SignIn({
|
|
|
1222
1322
|
)
|
|
1223
1323
|
] }) : null
|
|
1224
1324
|
] }),
|
|
1225
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "b316-button", type: "submit", disabled: busy, children: busy ? "Working\u2026" : pendingChallenge ? "Verify" : mode === "magic" ? "Email me a link" : "Sign in" }),
|
|
1325
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }),
|
|
1226
1326
|
passkeyReady && !pendingChallenge ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1227
1327
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "b316-muted", style: { textAlign: "center", margin: "10px 0" }, children: "or" }),
|
|
1228
1328
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -1837,6 +1937,7 @@ var import_react10 = require("react");
|
|
|
1837
1937
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1838
1938
|
function TwoFactorManager({
|
|
1839
1939
|
enabled = false,
|
|
1940
|
+
allowSms = false,
|
|
1840
1941
|
onEnabled,
|
|
1841
1942
|
onDisabled,
|
|
1842
1943
|
className,
|
|
@@ -1846,6 +1947,7 @@ function TwoFactorManager({
|
|
|
1846
1947
|
const [stage, setStage] = (0, import_react10.useState)({ name: "idle" });
|
|
1847
1948
|
const [on, setOn] = (0, import_react10.useState)(enabled);
|
|
1848
1949
|
const [code, setCode] = (0, import_react10.useState)("");
|
|
1950
|
+
const [phone, setPhone] = (0, import_react10.useState)("");
|
|
1849
1951
|
const [error, setError] = (0, import_react10.useState)(null);
|
|
1850
1952
|
const [busy, setBusy] = (0, import_react10.useState)(false);
|
|
1851
1953
|
if (!token) {
|
|
@@ -1875,6 +1977,21 @@ function TwoFactorManager({
|
|
|
1875
1977
|
setBusy(false);
|
|
1876
1978
|
}
|
|
1877
1979
|
}
|
|
1980
|
+
async function beginSms() {
|
|
1981
|
+
setBusy(true);
|
|
1982
|
+
setError(null);
|
|
1983
|
+
try {
|
|
1984
|
+
const enrollment = await client.auth.mfa.enrollSms(
|
|
1985
|
+
token,
|
|
1986
|
+
phone.trim() ? { phone: phone.trim() } : {}
|
|
1987
|
+
);
|
|
1988
|
+
setStage({ name: "enrollingEmail", ...enrollment });
|
|
1989
|
+
} catch (cause) {
|
|
1990
|
+
setError(cause instanceof Error ? cause.message : "Could not send a code.");
|
|
1991
|
+
} finally {
|
|
1992
|
+
setBusy(false);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1878
1995
|
async function confirm() {
|
|
1879
1996
|
if (stage.name !== "enrolling" && stage.name !== "enrollingEmail") {
|
|
1880
1997
|
return;
|
|
@@ -1917,6 +2034,33 @@ function TwoFactorManager({
|
|
|
1917
2034
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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." }),
|
|
1918
2035
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "b316-mfa-code-list", children: stage.recoveryCodes.map((recovery) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { children: recovery }) }, recovery)) }),
|
|
1919
2036
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "b316-button", onClick: () => setStage({ name: "idle" }), children: "I have saved them" })
|
|
2037
|
+
] }) : stage.name === "smsNumber" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "b316-mfa-enrol", children: [
|
|
2038
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { children: "We will text a code to this number now, and each time you sign in." }),
|
|
2039
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("label", { className: "b316-label", htmlFor: "b316-mfa-phone", children: "Mobile number" }),
|
|
2040
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2041
|
+
"input",
|
|
2042
|
+
{
|
|
2043
|
+
id: "b316-mfa-phone",
|
|
2044
|
+
className: "b316-input",
|
|
2045
|
+
type: "tel",
|
|
2046
|
+
autoComplete: "tel",
|
|
2047
|
+
placeholder: "+14155550123",
|
|
2048
|
+
value: phone,
|
|
2049
|
+
onChange: (event) => setPhone(event.target.value)
|
|
2050
|
+
}
|
|
2051
|
+
),
|
|
2052
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "b316-mfa-actions", children: [
|
|
2053
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2054
|
+
"button",
|
|
2055
|
+
{
|
|
2056
|
+
type: "button",
|
|
2057
|
+
className: "b316-button-quiet",
|
|
2058
|
+
onClick: () => setStage({ name: "idle" }),
|
|
2059
|
+
children: "Cancel"
|
|
2060
|
+
}
|
|
2061
|
+
),
|
|
2062
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "b316-button", disabled: busy, onClick: beginSms, children: busy ? "Sending\u2026" : "Send code" })
|
|
2063
|
+
] })
|
|
1920
2064
|
] }) : stage.name === "enrollingEmail" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "b316-mfa-enrol", children: [
|
|
1921
2065
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { children: [
|
|
1922
2066
|
"We sent a code to ",
|
|
@@ -2018,7 +2162,17 @@ function TwoFactorManager({
|
|
|
2018
2162
|
onClick: beginEmail,
|
|
2019
2163
|
children: "Email me a code"
|
|
2020
2164
|
}
|
|
2021
|
-
)
|
|
2165
|
+
),
|
|
2166
|
+
allowSms ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2167
|
+
"button",
|
|
2168
|
+
{
|
|
2169
|
+
type: "button",
|
|
2170
|
+
className: "b316-button-quiet",
|
|
2171
|
+
disabled: busy,
|
|
2172
|
+
onClick: () => setStage({ name: "smsNumber" }),
|
|
2173
|
+
children: "Text me a code"
|
|
2174
|
+
}
|
|
2175
|
+
) : null
|
|
2022
2176
|
] })
|
|
2023
2177
|
] })
|
|
2024
2178
|
] });
|
|
@@ -2350,6 +2504,16 @@ function TeamMembers({
|
|
|
2350
2504
|
}
|
|
2351
2505
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: cls("b316-card b316-team", className), style, children: [
|
|
2352
2506
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "b316-team-title", children: team?.name ?? "Team" }),
|
|
2507
|
+
team?.inherited ? (
|
|
2508
|
+
// Not a member of this team, so not in the list below and with nothing
|
|
2509
|
+
// to leave: access comes from the team that manages it, and ends there.
|
|
2510
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "b316-muted", children: [
|
|
2511
|
+
"You have access through",
|
|
2512
|
+
" ",
|
|
2513
|
+
teams.find((candidate) => candidate.id === team.via)?.name ?? "the organization that manages this team",
|
|
2514
|
+
"."
|
|
2515
|
+
] })
|
|
2516
|
+
) : null,
|
|
2353
2517
|
error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "b316-error", role: "alert", children: error }) : null,
|
|
2354
2518
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "b316-team-list", children: (members ?? []).map((member) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("li", { className: "b316-team-member", children: [
|
|
2355
2519
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
|
|
@@ -2706,6 +2870,7 @@ function usePlans(options = {}) {
|
|
|
2706
2870
|
const [busyPlanVersionId, setBusy] = (0, import_react18.useState)(null);
|
|
2707
2871
|
const [lastError, setLastError] = (0, import_react18.useState)(null);
|
|
2708
2872
|
const [ownPlanVersionId, setOwnPlanVersionId] = (0, import_react18.useState)(null);
|
|
2873
|
+
const [managedBy, setManagedBy] = (0, import_react18.useState)(null);
|
|
2709
2874
|
const [reloadCount, setReloadCount] = (0, import_react18.useState)(0);
|
|
2710
2875
|
const supplied = options.currentPlanVersionId;
|
|
2711
2876
|
const discover = supplied === void 0;
|
|
@@ -2726,13 +2891,16 @@ function usePlans(options = {}) {
|
|
|
2726
2891
|
};
|
|
2727
2892
|
}, [client, reloadCount]);
|
|
2728
2893
|
(0, import_react18.useEffect)(() => {
|
|
2729
|
-
if (!
|
|
2894
|
+
if (!token) {
|
|
2730
2895
|
return;
|
|
2731
2896
|
}
|
|
2732
2897
|
let cancelled = false;
|
|
2733
2898
|
client.billing.subscription(token).then((result) => {
|
|
2734
2899
|
if (!cancelled) {
|
|
2735
|
-
|
|
2900
|
+
setManagedBy(result.managedBy ?? null);
|
|
2901
|
+
if (discover) {
|
|
2902
|
+
setOwnPlanVersionId(result.subscription?.planVersion.id ?? null);
|
|
2903
|
+
}
|
|
2736
2904
|
}
|
|
2737
2905
|
}).catch(() => null);
|
|
2738
2906
|
return () => {
|
|
@@ -2746,6 +2914,11 @@ function usePlans(options = {}) {
|
|
|
2746
2914
|
const selectPlan = (0, import_react18.useCallback)(
|
|
2747
2915
|
async (plan) => {
|
|
2748
2916
|
setLastError(null);
|
|
2917
|
+
if (managedBy) {
|
|
2918
|
+
const reason = `Your plan is provided by ${managedBy.name ?? "the organization that manages your account"}.`;
|
|
2919
|
+
setLastError(reason);
|
|
2920
|
+
return { status: "failed", reason };
|
|
2921
|
+
}
|
|
2749
2922
|
if (!token) {
|
|
2750
2923
|
return { status: "needs-sign-in" };
|
|
2751
2924
|
}
|
|
@@ -2782,7 +2955,7 @@ function usePlans(options = {}) {
|
|
|
2782
2955
|
setBusy(null);
|
|
2783
2956
|
}
|
|
2784
2957
|
},
|
|
2785
|
-
[client, token, options.navigate]
|
|
2958
|
+
[client, token, options.navigate, managedBy]
|
|
2786
2959
|
);
|
|
2787
2960
|
return {
|
|
2788
2961
|
plans,
|
|
@@ -2792,6 +2965,7 @@ function usePlans(options = {}) {
|
|
|
2792
2965
|
busyPlanVersionId,
|
|
2793
2966
|
lastError,
|
|
2794
2967
|
signedIn: token !== null,
|
|
2968
|
+
managedBy,
|
|
2795
2969
|
selectPlan,
|
|
2796
2970
|
reload
|
|
2797
2971
|
};
|
|
@@ -2831,6 +3005,7 @@ function PricingTable({
|
|
|
2831
3005
|
busyPlanVersionId: busy,
|
|
2832
3006
|
lastError,
|
|
2833
3007
|
signedIn,
|
|
3008
|
+
managedBy,
|
|
2834
3009
|
selectPlan
|
|
2835
3010
|
} = usePlans({ currentPlanVersionId });
|
|
2836
3011
|
async function choose(plan) {
|
|
@@ -2856,7 +3031,16 @@ function PricingTable({
|
|
|
2856
3031
|
const anyDescription = plans.some((plan) => Boolean(plan.description));
|
|
2857
3032
|
const anyFeatured = plans.some((plan) => plan.key === featuredPlanKey);
|
|
2858
3033
|
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: cls("b316-plans", className), style, children: [
|
|
2859
|
-
|
|
3034
|
+
managedBy ? (
|
|
3035
|
+
// A client company's plan comes from the firm that runs it. The table
|
|
3036
|
+
// still shows what is on offer, with the current plan marked, but
|
|
3037
|
+
// nothing here can be bought: it would grant this account nothing.
|
|
3038
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "b316-plans-managed b316-muted", children: [
|
|
3039
|
+
"Your plan is provided by ",
|
|
3040
|
+
managedBy.name ?? "the organization that manages your account",
|
|
3041
|
+
". Ask them to change it."
|
|
3042
|
+
] })
|
|
3043
|
+
) : lastError ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "b316-plans-error", role: "alert", children: lastError }) : null,
|
|
2860
3044
|
plans.map((plan) => {
|
|
2861
3045
|
const base = plan.prices.find((price) => price.featureId === null) ?? plan.prices[0];
|
|
2862
3046
|
const isCurrent = current === plan.planVersionId;
|
|
@@ -2885,7 +3069,7 @@ function PricingTable({
|
|
|
2885
3069
|
{
|
|
2886
3070
|
type: "button",
|
|
2887
3071
|
className: isCurrent ? "b316-button b316-button-current" : "b316-button",
|
|
2888
|
-
disabled: isCurrent || busy !== null || !signedIn && !signInUrl && !onSelectPlan,
|
|
3072
|
+
disabled: isCurrent || managedBy !== null || busy !== null || !signedIn && !signInUrl && !onSelectPlan,
|
|
2889
3073
|
onClick: () => {
|
|
2890
3074
|
void choose(plan);
|
|
2891
3075
|
},
|
|
@@ -4217,6 +4401,7 @@ function SubscriptionManager({
|
|
|
4217
4401
|
const { client, token, loading: sessionLoading } = useBase316();
|
|
4218
4402
|
const [subscription, setSubscription] = (0, import_react26.useState)(null);
|
|
4219
4403
|
const [loading, setLoading] = (0, import_react26.useState)(true);
|
|
4404
|
+
const [managedBy, setManagedBy] = (0, import_react26.useState)(null);
|
|
4220
4405
|
const [busy, setBusy] = (0, import_react26.useState)(false);
|
|
4221
4406
|
const [error, setError] = (0, import_react26.useState)(null);
|
|
4222
4407
|
const load = (0, import_react26.useCallback)(async () => {
|
|
@@ -4228,6 +4413,7 @@ function SubscriptionManager({
|
|
|
4228
4413
|
try {
|
|
4229
4414
|
const result = await client.billing.subscription(token);
|
|
4230
4415
|
setSubscription(result.subscription);
|
|
4416
|
+
setManagedBy(result.managedBy ?? null);
|
|
4231
4417
|
} catch (cause) {
|
|
4232
4418
|
console.error("[base316] could not load the subscription", cause);
|
|
4233
4419
|
setError("Could not load your subscription.");
|
|
@@ -4259,7 +4445,11 @@ function SubscriptionManager({
|
|
|
4259
4445
|
" \xB7 ",
|
|
4260
4446
|
subscription.cancelAtPeriodEnd ? `Ends ${renews}` : `Renews ${renews}`
|
|
4261
4447
|
] }),
|
|
4262
|
-
|
|
4448
|
+
managedBy ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "b316-muted", children: [
|
|
4449
|
+
"Provided by ",
|
|
4450
|
+
managedBy.name ?? "the organization that manages your account",
|
|
4451
|
+
". Ask them to change or cancel it."
|
|
4452
|
+
] }) : subscription.cancelAtPeriodEnd ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "b316-muted", children: [
|
|
4263
4453
|
"This subscription will not renew. You keep access until ",
|
|
4264
4454
|
renews,
|
|
4265
4455
|
"."
|
|
@@ -4596,6 +4786,11 @@ var MANIFEST = [
|
|
|
4596
4786
|
summary: "Colours, radius and font as tokens. Restyles everything at once.",
|
|
4597
4787
|
required: false
|
|
4598
4788
|
},
|
|
4789
|
+
{
|
|
4790
|
+
name: "tenant",
|
|
4791
|
+
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.",
|
|
4792
|
+
required: false
|
|
4793
|
+
},
|
|
4599
4794
|
...appearance
|
|
4600
4795
|
],
|
|
4601
4796
|
seeAlso: ["useBase316", "useSession"]
|
|
@@ -4621,7 +4816,7 @@ var MANIFEST = [
|
|
|
4621
4816
|
{
|
|
4622
4817
|
name: "SignIn",
|
|
4623
4818
|
kind: "component",
|
|
4624
|
-
summary: "Password, passkey
|
|
4819
|
+
summary: "Password, passkey, magic-link and text-message sign-in in one form.",
|
|
4625
4820
|
useWhen: "The application renders its own sign-in rather than redirecting to the hosted page.",
|
|
4626
4821
|
requires: [],
|
|
4627
4822
|
props: [
|
|
@@ -4636,6 +4831,11 @@ var MANIFEST = [
|
|
|
4636
4831
|
summary: "Where an emailed link lands, which must be registered on the environment. Without it, magic link is not offered at all.",
|
|
4637
4832
|
required: false
|
|
4638
4833
|
},
|
|
4834
|
+
{
|
|
4835
|
+
name: "phone",
|
|
4836
|
+
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.",
|
|
4837
|
+
required: false
|
|
4838
|
+
},
|
|
4639
4839
|
...appearance
|
|
4640
4840
|
],
|
|
4641
4841
|
seeAlso: ["SignUp", "ForgotPassword"]
|
|
@@ -4769,8 +4969,8 @@ var MANIFEST = [
|
|
|
4769
4969
|
{
|
|
4770
4970
|
name: "TwoFactorManager",
|
|
4771
4971
|
kind: "component",
|
|
4772
|
-
summary: "Turn two-factor authentication on or off, by authenticator app
|
|
4773
|
-
useWhen: "A security settings page.
|
|
4972
|
+
summary: "Turn two-factor authentication on or off, by authenticator app, a code to the inbox or a code by text message.",
|
|
4973
|
+
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.",
|
|
4774
4974
|
requires: ["session"],
|
|
4775
4975
|
props: [
|
|
4776
4976
|
{
|
|
@@ -4778,6 +4978,11 @@ var MANIFEST = [
|
|
|
4778
4978
|
summary: "Whether a factor already exists, which decides which side is shown.",
|
|
4779
4979
|
required: false
|
|
4780
4980
|
},
|
|
4981
|
+
{
|
|
4982
|
+
name: "allowSms",
|
|
4983
|
+
summary: "Offer a code by text message. Off by default: it only works once the environment can send one.",
|
|
4984
|
+
required: false
|
|
4985
|
+
},
|
|
4781
4986
|
{ name: "onEnabled", summary: "Called once a factor is confirmed.", required: false },
|
|
4782
4987
|
{ name: "onDisabled", summary: "Called once it is removed.", required: false },
|
|
4783
4988
|
...appearance
|
|
@@ -4798,7 +5003,7 @@ var MANIFEST = [
|
|
|
4798
5003
|
{
|
|
4799
5004
|
name: "Branding",
|
|
4800
5005
|
kind: "component",
|
|
4801
|
-
summary: "The
|
|
5006
|
+
summary: "The logo, name and tagline \u2014 the environment's, or the tenant's when the provider is given a `tenant`.",
|
|
4802
5007
|
useWhen: "Above a sign-in form, so your pages match the hosted ones.",
|
|
4803
5008
|
requires: [],
|
|
4804
5009
|
props: [
|
|
@@ -4810,7 +5015,7 @@ var MANIFEST = [
|
|
|
4810
5015
|
{
|
|
4811
5016
|
name: "TeamMembers",
|
|
4812
5017
|
kind: "component",
|
|
4813
|
-
summary: "Who is in a team, who has been invited, and the way in and out.",
|
|
5018
|
+
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.",
|
|
4814
5019
|
useWhen: "A Team or Members settings page. One component covers the list, the pending invitations, the invite box and leaving.",
|
|
4815
5020
|
requires: ["session"],
|
|
4816
5021
|
props: [
|