@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/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.branding().then((result) => {
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 { isPasskeyAutofillAvailable, isPasskeySupported, needsSecondFactor } from "@base316/sdk";
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." : "That email and password did not match."
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
- magicAvailable ? /* @__PURE__ */ jsxs3("div", { className: "b316-tabs", children: [
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 (!discover || !token) {
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
- setOwnPlanVersionId(result.subscription?.planVersion.id ?? null);
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
- lastError ? /* @__PURE__ */ jsx19("div", { className: "b316-plans-error", role: "alert", children: lastError }) : null,
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
- subscription.cancelAtPeriodEnd ? /* @__PURE__ */ jsxs26("p", { className: "b316-muted", children: [
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 and magic-link sign-in in one form.",
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 or a code to the inbox.",
4730
- useWhen: "A security settings page. Offers both factors; the app is stronger, the emailed code is the one people who will not install an app actually use.",
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 environment's logo, name and tagline.",
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: [