@flamingo-stack/openframe-frontend-core 0.0.749 → 0.0.750
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/{chunk-SOPVUD2Y.cjs → chunk-4O2KYY4K.cjs} +3 -3
- package/dist/{chunk-SECT4H34.cjs → chunk-6KEJQR6L.cjs} +96 -41
- package/dist/{chunk-OZLIKBKC.cjs → chunk-7RCTOWBA.cjs} +3 -3
- package/dist/{chunk-ZLF55RDR.cjs → chunk-AW6GP3JK.cjs} +12 -12
- package/dist/{chunk-SGL47BS2.cjs → chunk-BO6ZOAPB.cjs} +2 -2
- package/dist/{chunk-KRAQEZPB.cjs → chunk-CNUZLOVA.cjs} +10 -10
- package/dist/{chunk-VEVIMDNK.cjs → chunk-FP6HAJQV.cjs} +23 -23
- package/dist/{chunk-BN6NA5RM.cjs → chunk-GVETSV7H.cjs} +15 -15
- package/dist/{chunk-CXV7VBSI.cjs → chunk-JPUTILTV.cjs} +22 -22
- package/dist/{chunk-GOFEOCQN.cjs → chunk-KVSEWACW.cjs} +4 -4
- package/dist/{chunk-LQT2ITMG.cjs → chunk-YL62VAHC.cjs} +2 -2
- package/dist/{cloud-tenants-2MYFHEYY.cjs → cloud-tenants-E4JSG3GH.cjs} +2 -2
- package/dist/components/case-studies/index.cjs +10 -10
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/features/auth/login-form.d.ts +34 -21
- package/dist/components/features/auth/login-form.d.ts.map +1 -1
- package/dist/components/features/auth/login-form.js +77 -39
- package/dist/components/features/auth/sso-providers.d.ts +7 -1
- package/dist/components/features/auth/sso-providers.d.ts.map +1 -1
- package/dist/components/features/auth/sso-providers.js +22 -4
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/help-center-pages/index.cjs +44 -44
- package/dist/components/index.cjs +17 -17
- package/dist/components/meeting-scheduler/index.cjs +13 -13
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/onboarding-guides/index.cjs +4 -4
- package/dist/components/product-demo/index.cjs +7 -7
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/ui/index.cjs +2 -2
- package/dist/{devices-FVXAGKVX.cjs → devices-AVRQGB2N.cjs} +3 -3
- package/dist/index.cjs +3 -3
- package/dist/{knowledge-ZL3ZHBSL.cjs → knowledge-S3CNISQH.cjs} +2 -2
- package/dist/{logs-KQCWDIOB.cjs → logs-WDLOKDXT.cjs} +10 -10
- package/dist/{policies-ARDCZ3CT.cjs → policies-STML7NEZ.cjs} +6 -6
- package/dist/{scripts-ZAMVWCY4.cjs → scripts-HO53VYGX.cjs} +7 -7
- package/dist/{software-update-6F7BIYS3.cjs → software-update-4VTK7LWQ.cjs} +5 -5
- package/package.json +1 -1
- package/src/components/features/auth/login-form.tsx +121 -64
- package/src/components/features/auth/sso-providers.tsx +21 -0
|
@@ -16,7 +16,7 @@ var _chunkDKOLQPE7cjs = require('./chunk-DKOLQPE7.cjs');
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
var
|
|
19
|
+
var _chunk6KEJQR6Lcjs = require('./chunk-6KEJQR6L.cjs');
|
|
20
20
|
require('./chunk-C6LPOYDM.cjs');
|
|
21
21
|
require('./chunk-NTGIQP7O.cjs');
|
|
22
22
|
require('./chunk-ALPRQ3ZX.cjs');
|
|
@@ -1888,7 +1888,7 @@ function entry(logId, minutesAgo, severity, toolType, device2, summary) {
|
|
|
1888
1888
|
id: logId,
|
|
1889
1889
|
logId,
|
|
1890
1890
|
timestamp: displayTime(_chunkDKOLQPE7cjs.demoMinutesAgo.call(void 0, minutesAgo)),
|
|
1891
|
-
status: { label: severity, variant:
|
|
1891
|
+
status: { label: severity, variant: _chunk6KEJQR6Lcjs.logSeverityVariant.call(void 0, severity) },
|
|
1892
1892
|
source: { name: toolType, toolType },
|
|
1893
1893
|
device: device2,
|
|
1894
1894
|
description: { title: summary }
|
|
@@ -1960,13 +1960,13 @@ var NO_FILTERS = {};
|
|
|
1960
1960
|
var ACTION_COLUMNS = ["copy", "quickView", "open"];
|
|
1961
1961
|
var BESIDE_DRAWER_HIDDEN = [...ACTION_COLUMNS, "tool", "source"];
|
|
1962
1962
|
var logHref = () => "#";
|
|
1963
|
-
var copyAction = () => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1963
|
+
var copyAction = () => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6KEJQR6Lcjs.LogCopyIconButton, {});
|
|
1964
1964
|
function LogsScreen({ compact = false }) {
|
|
1965
1965
|
const [dock, setDock] = _react.useState.call(void 0, null);
|
|
1966
1966
|
const selected = LOGS_FIXTURE_SELECTED;
|
|
1967
1967
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex h-full bg-ods-bg", children: [
|
|
1968
1968
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "min-w-0 flex-1 overflow-clip", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1969
|
-
|
|
1969
|
+
_chunk6KEJQR6Lcjs.LogsPageView,
|
|
1970
1970
|
{
|
|
1971
1971
|
showHeader: false,
|
|
1972
1972
|
className: "p-[var(--spacing-system-l)]",
|
|
@@ -1974,7 +1974,7 @@ function LogsScreen({ compact = false }) {
|
|
|
1974
1974
|
onSearchChange: noop,
|
|
1975
1975
|
onOpenFilters: noop,
|
|
1976
1976
|
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1977
|
-
|
|
1977
|
+
_chunk6KEJQR6Lcjs.LogsTableView,
|
|
1978
1978
|
{
|
|
1979
1979
|
logs: LOGS_FIXTURE_ENTRIES,
|
|
1980
1980
|
facets: LOGS_FIXTURE_FACETS,
|
|
@@ -1988,8 +1988,8 @@ function LogsScreen({ compact = false }) {
|
|
|
1988
1988
|
)
|
|
1989
1989
|
}
|
|
1990
1990
|
) }),
|
|
1991
|
-
!compact && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { ref: setDock, className: "relative w-[calc(400px+var(--spacing-system-m))] shrink-0", children: dock && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1992
|
-
|
|
1991
|
+
!compact && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { ref: setDock, className: "relative w-[calc(400px+var(--spacing-system-m))] shrink-0", children: dock && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6KEJQR6Lcjs.AppLayoutDrawerContainerContext.Provider, { value: dock, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1992
|
+
_chunk6KEJQR6Lcjs.LogDrawer,
|
|
1993
1993
|
{
|
|
1994
1994
|
docked: true,
|
|
1995
1995
|
isOpen: true,
|
|
@@ -2001,12 +2001,12 @@ function LogsScreen({ compact = false }) {
|
|
|
2001
2001
|
{ label: "Log ID", value: selected.logId },
|
|
2002
2002
|
{
|
|
2003
2003
|
label: "Source",
|
|
2004
|
-
value: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
2004
|
+
value: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6KEJQR6Lcjs.ToolBadge, { toolType: _chunkWZX26ODJcjs.normalizeToolTypeWithFallback.call(void 0, selected.source.toolType) })
|
|
2005
2005
|
},
|
|
2006
|
-
{ label: "Device", value:
|
|
2006
|
+
{ label: "Device", value: _chunk6KEJQR6Lcjs.logSourceLabels.call(void 0, selected.device).deviceName }
|
|
2007
2007
|
],
|
|
2008
2008
|
deviceCard: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
2009
|
-
|
|
2009
|
+
_chunk6KEJQR6Lcjs.DeviceCard,
|
|
2010
2010
|
{
|
|
2011
2011
|
...LOGS_FIXTURE_DEVICE,
|
|
2012
2012
|
actions: {
|
|
@@ -10,7 +10,7 @@ var _chunkDKOLQPE7cjs = require('./chunk-DKOLQPE7.cjs');
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _chunk6KEJQR6Lcjs = require('./chunk-6KEJQR6L.cjs');
|
|
14
14
|
require('./chunk-C6LPOYDM.cjs');
|
|
15
15
|
require('./chunk-NTGIQP7O.cjs');
|
|
16
16
|
require('./chunk-ALPRQ3ZX.cjs');
|
|
@@ -1893,14 +1893,14 @@ var POLICIES = [
|
|
|
1893
1893
|
policy("policy-local-admins", "No local admins", "No everyday account is a local administrator.", false, 0)
|
|
1894
1894
|
];
|
|
1895
1895
|
var POLICIES_FIXTURE = {
|
|
1896
|
-
summary:
|
|
1896
|
+
summary: _chunk6KEJQR6Lcjs.computePolicySummary.call(void 0, POLICIES),
|
|
1897
1897
|
rows: POLICIES.map((item) => ({
|
|
1898
1898
|
id: item.id,
|
|
1899
1899
|
name: item.name,
|
|
1900
1900
|
description: item.description,
|
|
1901
1901
|
critical: item.critical,
|
|
1902
1902
|
severityLabel: item.critical ? "Critical" : "Low",
|
|
1903
|
-
status:
|
|
1903
|
+
status: _chunk6KEJQR6Lcjs.getPolicyTableStatus.call(void 0, item),
|
|
1904
1904
|
actions: [
|
|
1905
1905
|
{ label: "Policy Details", onClick: noop },
|
|
1906
1906
|
{ label: "Delete Policy", onClick: noop }
|
|
@@ -1914,16 +1914,16 @@ var POLICIES_FIXTURE = {
|
|
|
1914
1914
|
var _jsxruntime = require('react/jsx-runtime');
|
|
1915
1915
|
function PoliciesScreen({ compact = false }) {
|
|
1916
1916
|
const table = /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1917
|
-
|
|
1917
|
+
_chunk6KEJQR6Lcjs.PoliciesTable,
|
|
1918
1918
|
{
|
|
1919
1919
|
rows: POLICIES_FIXTURE.rows,
|
|
1920
1920
|
rowAsLink: true,
|
|
1921
|
-
rightSlot: compact ? void 0 : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1921
|
+
rightSlot: compact ? void 0 : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6KEJQR6Lcjs.DataTable.RowCount, {}),
|
|
1922
1922
|
emptyMessage: "No policies found."
|
|
1923
1923
|
}
|
|
1924
1924
|
);
|
|
1925
1925
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex h-full flex-col gap-[var(--spacing-system-l)] bg-ods-bg p-[var(--spacing-system-l)]", children: [
|
|
1926
|
-
!compact && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1926
|
+
!compact && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6KEJQR6Lcjs.PolicySummaryCards, { summary: POLICIES_FIXTURE.summary, now: POLICIES_FIXTURE.now }),
|
|
1927
1927
|
table
|
|
1928
1928
|
] });
|
|
1929
1929
|
}
|
|
@@ -13,7 +13,7 @@ var _chunkDKOLQPE7cjs = require('./chunk-DKOLQPE7.cjs');
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
var
|
|
16
|
+
var _chunk6KEJQR6Lcjs = require('./chunk-6KEJQR6L.cjs');
|
|
17
17
|
require('./chunk-C6LPOYDM.cjs');
|
|
18
18
|
require('./chunk-NTGIQP7O.cjs');
|
|
19
19
|
|
|
@@ -1997,8 +1997,8 @@ function buildScriptsFixture(cast) {
|
|
|
1997
1997
|
var _jsxruntime = require('react/jsx-runtime');
|
|
1998
1998
|
var WIDE_HIDDEN_COLUMNS = ["result", "open"];
|
|
1999
1999
|
var COMPACT_COLUMNS = [
|
|
2000
|
-
{ ...
|
|
2001
|
-
|
|
2000
|
+
{ ..._chunk6KEJQR6Lcjs.EXECUTION_COLUMNS.machineId, width: "flex-1 min-w-0", hideAt: void 0 },
|
|
2001
|
+
_chunk6KEJQR6Lcjs.EXECUTION_COLUMNS.status
|
|
2002
2002
|
];
|
|
2003
2003
|
var COMPACT_ROWS = 4;
|
|
2004
2004
|
var TOKEN_CLASS = {
|
|
@@ -2057,7 +2057,7 @@ function ScriptCodePicture({ script }) {
|
|
|
2057
2057
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex min-w-0 items-center justify-between gap-[var(--spacing-system-m)]", children: [
|
|
2058
2058
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkALPRQ3ZXcjs.TruncateText, { variant: "h6", tone: "secondary", children: `Written by ${provenance.author} \xB7 approved by ${provenance.approver}` }),
|
|
2059
2059
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
2060
|
-
|
|
2060
|
+
_chunk6KEJQR6Lcjs.ScriptShellBadge,
|
|
2061
2061
|
{
|
|
2062
2062
|
value: script.shellType,
|
|
2063
2063
|
className: "shrink-0 text-ods-text-secondary text-h6",
|
|
@@ -2081,7 +2081,7 @@ function ScriptsScreen({ compact = false }) {
|
|
|
2081
2081
|
return (
|
|
2082
2082
|
// Four runs of the card's row height fill the frame; the padding is what is left round them.
|
|
2083
2083
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-full overflow-hidden bg-ods-bg p-[var(--spacing-system-xsf)]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
2084
|
-
|
|
2084
|
+
_chunk6KEJQR6Lcjs.ScriptExecutionsTable,
|
|
2085
2085
|
{
|
|
2086
2086
|
rows: fixture.executions.slice(0, COMPACT_ROWS),
|
|
2087
2087
|
columns: COMPACT_COLUMNS,
|
|
@@ -2091,13 +2091,13 @@ function ScriptsScreen({ compact = false }) {
|
|
|
2091
2091
|
) })
|
|
2092
2092
|
);
|
|
2093
2093
|
}
|
|
2094
|
-
const renderOutput = (row) => row.id === fixture.openExecutionId ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
2094
|
+
const renderOutput = (row) => row.id === fixture.openExecutionId ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6KEJQR6Lcjs.ScriptExecutionResult, { result: row.result, empty: "\u2014" }) : null;
|
|
2095
2095
|
return (
|
|
2096
2096
|
// The frame's tighter padding and no header row: the output opened under a run needs the height.
|
|
2097
2097
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex h-full gap-[var(--spacing-system-m)] overflow-hidden bg-ods-bg p-[var(--spacing-system-m)]", children: [
|
|
2098
2098
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-[39%] shrink-0", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ScriptCodePicture, { script: fixture.script }) }),
|
|
2099
2099
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "min-w-0 flex-1 overflow-hidden", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
2100
|
-
|
|
2100
|
+
_chunk6KEJQR6Lcjs.ScriptExecutionsTable,
|
|
2101
2101
|
{
|
|
2102
2102
|
rows: fixture.executions,
|
|
2103
2103
|
hideColumns: WIDE_HIDDEN_COLUMNS,
|
|
@@ -15,7 +15,7 @@ var _chunkDKOLQPE7cjs = require('./chunk-DKOLQPE7.cjs');
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
var
|
|
18
|
+
var _chunk6KEJQR6Lcjs = require('./chunk-6KEJQR6L.cjs');
|
|
19
19
|
require('./chunk-C6LPOYDM.cjs');
|
|
20
20
|
require('./chunk-NTGIQP7O.cjs');
|
|
21
21
|
|
|
@@ -1924,12 +1924,12 @@ function StaticPackageField({ value }) {
|
|
|
1924
1924
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1925
1925
|
_chunkALPRQ3ZXcjs.Autocomplete,
|
|
1926
1926
|
{
|
|
1927
|
-
label:
|
|
1927
|
+
label: _chunk6KEJQR6Lcjs.PACKAGE_SEARCH_LABEL,
|
|
1928
1928
|
labelVariant: "large",
|
|
1929
1929
|
options: value ? [{ value: value.id, label: value.name, description: _nullishCoalesce(value.description, () => ( void 0)) }] : [],
|
|
1930
1930
|
value: _nullishCoalesce(_optionalChain([value, 'optionalAccess', _ => _.id]), () => ( null)),
|
|
1931
1931
|
onChange: noop,
|
|
1932
|
-
placeholder:
|
|
1932
|
+
placeholder: _chunk6KEJQR6Lcjs.PACKAGE_SEARCH_PLACEHOLDER
|
|
1933
1933
|
}
|
|
1934
1934
|
);
|
|
1935
1935
|
}
|
|
@@ -1943,7 +1943,7 @@ function SoftwareUpdateScreen({ compact = false }) {
|
|
|
1943
1943
|
[fixture.devices, fixture.selectedIds]
|
|
1944
1944
|
);
|
|
1945
1945
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-full bg-ods-bg pt-[var(--spacing-system-l)]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-full", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1946
|
-
|
|
1946
|
+
_chunk6KEJQR6Lcjs.SoftwareActionForm,
|
|
1947
1947
|
{
|
|
1948
1948
|
action: fixture.action,
|
|
1949
1949
|
showHeader: false,
|
|
@@ -1955,7 +1955,7 @@ function SoftwareUpdateScreen({ compact = false }) {
|
|
|
1955
1955
|
initialValues: fixture.initialValues,
|
|
1956
1956
|
renderPackageSearch,
|
|
1957
1957
|
renderDevicePicker: () => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1958
|
-
|
|
1958
|
+
_chunk6KEJQR6Lcjs.DeviceSelector,
|
|
1959
1959
|
{
|
|
1960
1960
|
devices: selected,
|
|
1961
1961
|
loading: false,
|
package/package.json
CHANGED
|
@@ -12,6 +12,25 @@ import { LegalLinks } from './legal-links';
|
|
|
12
12
|
import type { AuthSsoProvider } from './sso-providers';
|
|
13
13
|
import { SsoProviderButtons } from './sso-providers';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* How a block that appears in answer to the email eases in: the prototype's fade from 4px above, short
|
|
17
|
+
* enough to read as the same card changing rather than a new one arriving.
|
|
18
|
+
*/
|
|
19
|
+
const ENTER_ANIMATION = 'animate-in fade-in-0 slide-in-from-top-1 duration-200 ease-out motion-reduce:animate-none';
|
|
20
|
+
|
|
21
|
+
/** The sign-in methods of the organization an email resolved to. */
|
|
22
|
+
export interface LoginFormTenantMethods {
|
|
23
|
+
/** Names the block ("Sign-in methods for <name>") and tags the organization's own providers. */
|
|
24
|
+
name?: string;
|
|
25
|
+
/** Its built-in OpenFrame login, offered first. */
|
|
26
|
+
openframe: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Identity providers it configured with its own credentials, tagged with `name` so they cannot be
|
|
29
|
+
* mistaken for the generic button of the same provider.
|
|
30
|
+
*/
|
|
31
|
+
providers: Exclude<AuthSsoProvider, 'openframe'>[];
|
|
32
|
+
}
|
|
33
|
+
|
|
15
34
|
export interface LoginFormProps {
|
|
16
35
|
/** Controlled email value */
|
|
17
36
|
email: string;
|
|
@@ -24,37 +43,39 @@ export interface LoginFormProps {
|
|
|
24
43
|
};
|
|
25
44
|
/** Informational status under the email field (e.g. live tenant discovery). `errors.email` wins. */
|
|
26
45
|
emailStatus?: { message: string; variant: 'error' | 'warning' | 'success' | 'muted' };
|
|
46
|
+
/** A message too long for the field's one-line status, shown in a notice row under the field. */
|
|
47
|
+
notice?: string;
|
|
27
48
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
49
|
+
* The generic providers, under the email field behind `dividerLabel`. They need nothing typed: the
|
|
50
|
+
* server resolves the organization from the identity the provider asserts. With `tenantMethods`
|
|
51
|
+
* they follow the organization's own methods; pass `[]` to offer only those.
|
|
30
52
|
*/
|
|
31
53
|
ssoProviders: AuthSsoProvider[];
|
|
32
54
|
onSsoClick?: (provider: AuthSsoProvider) => void;
|
|
33
|
-
/** Disables every provider button (e.g.
|
|
55
|
+
/** Disables every provider button (e.g. while the typed email is being looked up). */
|
|
34
56
|
ssoDisabled?: boolean;
|
|
35
|
-
/** When set, only these providers are clickable; the rest stay disabled. */
|
|
57
|
+
/** When set, only these generic providers are clickable; the rest stay disabled. */
|
|
36
58
|
ssoEnabledProviders?: AuthSsoProvider[];
|
|
37
59
|
/** Verb prefix for provider buttons, e.g. "Continue with". */
|
|
38
60
|
ssoActionLabel?: string;
|
|
39
|
-
/** Label on the divider
|
|
61
|
+
/** Label on the divider above the generic providers. */
|
|
40
62
|
dividerLabel?: string;
|
|
41
63
|
/**
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Three states, and the empty array is not the same as nothing: `undefined` means discovery has
|
|
47
|
-
* not resolved a tenant yet and the slot stays empty, while `[]` means it resolved and the tenant
|
|
48
|
-
* has none — which is an answer worth showing rather than silence.
|
|
64
|
+
* The organization the typed email resolved to. Its methods render above the generic block, under
|
|
65
|
+
* a heading that names it, so the switch from "any account" to "this organization's sign-in" is
|
|
66
|
+
* visible rather than a silent change of what the same buttons do.
|
|
49
67
|
*/
|
|
50
|
-
|
|
51
|
-
/** Shown in place of the custom SSO buttons when the resolved tenant has none configured. */
|
|
52
|
-
noCustomSsoLabel?: string;
|
|
68
|
+
tenantMethods?: LoginFormTenantMethods;
|
|
53
69
|
/** Primary submit under the email field. Omit the handler to hide the button. */
|
|
54
70
|
onSubmitClick?: () => void;
|
|
55
71
|
submitLabel?: string;
|
|
56
72
|
/** Disables just the primary submit (fields stay editable). */
|
|
57
73
|
submitDisabled?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Enter in the email field when there is no submit button, e.g. to look the address up now
|
|
76
|
+
* instead of after a typing pause. With `onSubmitClick` set, Enter drives that instead.
|
|
77
|
+
*/
|
|
78
|
+
onEmailEnter?: () => void;
|
|
58
79
|
/** Overrides the default heading. */
|
|
59
80
|
title?: string;
|
|
60
81
|
/** Overrides the default sub-heading. */
|
|
@@ -70,11 +91,10 @@ export interface LoginFormProps {
|
|
|
70
91
|
}
|
|
71
92
|
|
|
72
93
|
/**
|
|
73
|
-
* Login form (Login tab). Presentational + controlled — the consumer owns
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* resolves the tenant from the identity the provider asserts.
|
|
94
|
+
* Login form (Login tab). Presentational + controlled — the consumer owns state, validation and
|
|
95
|
+
* discovery. Email first, sign-in methods under it: everything that changes once the email is known
|
|
96
|
+
* sits below the field, and the block changes as a whole. Before that the generic providers are
|
|
97
|
+
* there and need nothing typed; whether they are gated is the consumer's call via `ssoDisabled`.
|
|
78
98
|
*/
|
|
79
99
|
export function LoginForm({
|
|
80
100
|
email,
|
|
@@ -84,17 +104,18 @@ export function LoginForm({
|
|
|
84
104
|
disabled = false,
|
|
85
105
|
errors,
|
|
86
106
|
emailStatus,
|
|
107
|
+
notice,
|
|
87
108
|
ssoProviders,
|
|
88
109
|
onSsoClick,
|
|
89
110
|
ssoDisabled = false,
|
|
90
111
|
ssoEnabledProviders,
|
|
91
112
|
ssoActionLabel = 'Continue with',
|
|
92
|
-
dividerLabel = 'or continue with
|
|
93
|
-
|
|
94
|
-
noCustomSsoLabel = 'No additional SSO configurations for this domain',
|
|
113
|
+
dividerLabel = 'or continue with',
|
|
114
|
+
tenantMethods,
|
|
95
115
|
onSubmitClick,
|
|
96
116
|
submitLabel = 'Continue',
|
|
97
117
|
submitDisabled = false,
|
|
118
|
+
onEmailEnter,
|
|
98
119
|
title = 'Login to OpenFrame',
|
|
99
120
|
subtitle = 'Enter your email to access your organization.',
|
|
100
121
|
termsUrl,
|
|
@@ -102,15 +123,20 @@ export function LoginForm({
|
|
|
102
123
|
className,
|
|
103
124
|
}: LoginFormProps) {
|
|
104
125
|
const fieldDisabled = disabled || loading;
|
|
126
|
+
const providersDisabled = fieldDisabled || ssoDisabled;
|
|
105
127
|
|
|
106
128
|
// Validation messages are deferred while the user is typing (shown on blur or after a pause).
|
|
107
129
|
const emailErr = useDeferredError(errors?.email, email);
|
|
108
130
|
|
|
109
|
-
// Enter drives the same action as the visible button, under the same gate. A
|
|
110
|
-
//
|
|
131
|
+
// Enter drives the same action as the visible button, under the same gate. A held key's repeats
|
|
132
|
+
// are not new presses: each would re-run an action that can fail fast (a lookup while offline).
|
|
111
133
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
112
|
-
if (event.key !== 'Enter' ||
|
|
113
|
-
onSubmitClick
|
|
134
|
+
if (event.key !== 'Enter' || event.repeat || fieldDisabled) return;
|
|
135
|
+
if (onSubmitClick) {
|
|
136
|
+
if (!submitDisabled) onSubmitClick();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
onEmailEnter?.();
|
|
114
140
|
};
|
|
115
141
|
|
|
116
142
|
return (
|
|
@@ -126,21 +152,6 @@ export function LoginForm({
|
|
|
126
152
|
<p className="text-ods-text-secondary text-h4">{subtitle}</p>
|
|
127
153
|
</div>
|
|
128
154
|
|
|
129
|
-
{/* Providers come FIRST. They need nothing typed, and putting the email above them taught
|
|
130
|
-
people to fill it in before tapping one — the behaviour that made a provider button look
|
|
131
|
-
broken while it waited on a field it never needed. */}
|
|
132
|
-
{ssoProviders.length > 0 && (
|
|
133
|
-
<SsoProviderButtons
|
|
134
|
-
providers={ssoProviders}
|
|
135
|
-
onSsoClick={onSsoClick}
|
|
136
|
-
actionLabel={ssoActionLabel}
|
|
137
|
-
disabled={disabled || loading || ssoDisabled}
|
|
138
|
-
enabledProviders={ssoEnabledProviders}
|
|
139
|
-
/>
|
|
140
|
-
)}
|
|
141
|
-
|
|
142
|
-
{ssoProviders.length > 0 && <LabeledDivider label={dividerLabel} />}
|
|
143
|
-
|
|
144
155
|
{/* Email */}
|
|
145
156
|
<Input
|
|
146
157
|
type="email"
|
|
@@ -155,28 +166,21 @@ export function LoginForm({
|
|
|
155
166
|
onKeyDown={handleKeyDown}
|
|
156
167
|
/>
|
|
157
168
|
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// at phone width, and a fixed row clipped it.
|
|
174
|
-
className="flex min-h-11 w-full items-center gap-[var(--spacing-system-m)] rounded-md bg-ods-bg-surface px-[var(--spacing-system-s)] py-[var(--spacing-system-s)] text-ods-text-secondary text-h3 md:min-h-12"
|
|
175
|
-
>
|
|
176
|
-
<AlertTriangleIcon className="h-6 w-6 shrink-0" />
|
|
177
|
-
{noCustomSsoLabel}
|
|
178
|
-
</div>
|
|
179
|
-
))}
|
|
169
|
+
{notice && (
|
|
170
|
+
// Tokens are the design's: soft-grey surface, grey text and icon, the bold-body step, a 24px
|
|
171
|
+
// icon, and 12/16 for the inset and the gap.
|
|
172
|
+
<div
|
|
173
|
+
role="status"
|
|
174
|
+
// A minimum, not a fixed height: the notice can run to several lines at phone width.
|
|
175
|
+
className={cn(
|
|
176
|
+
'flex min-h-11 w-full items-center gap-[var(--spacing-system-m)] rounded-md bg-ods-bg-surface px-[var(--spacing-system-s)] py-[var(--spacing-system-s)] text-ods-text-secondary text-h3 md:min-h-12',
|
|
177
|
+
ENTER_ANIMATION,
|
|
178
|
+
)}
|
|
179
|
+
>
|
|
180
|
+
<AlertTriangleIcon className="h-6 w-6 shrink-0" />
|
|
181
|
+
{notice}
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
180
184
|
|
|
181
185
|
{onSubmitClick && (
|
|
182
186
|
// Full width on a phone, half the row and right-aligned from `md` up, matching the design.
|
|
@@ -197,6 +201,59 @@ export function LoginForm({
|
|
|
197
201
|
</div>
|
|
198
202
|
)}
|
|
199
203
|
|
|
204
|
+
{(tenantMethods || ssoProviders.length > 0) && (
|
|
205
|
+
// One block, so an organization's methods replace the generic set as a whole: the key mounts
|
|
206
|
+
// it anew when they arrive, and every row of it eases in, not only the rows that are new.
|
|
207
|
+
// Going back to the generic set swaps without a transition, as the prototype does.
|
|
208
|
+
<div
|
|
209
|
+
key={tenantMethods ? `tenant:${tenantMethods.name ?? ''}` : 'generic'}
|
|
210
|
+
role="group"
|
|
211
|
+
aria-label="Sign-in methods"
|
|
212
|
+
className={cn('flex flex-col gap-[var(--spacing-system-l)]', tenantMethods && ENTER_ANIMATION)}
|
|
213
|
+
>
|
|
214
|
+
{tenantMethods && (
|
|
215
|
+
<>
|
|
216
|
+
<LabeledDivider label={`Sign-in methods for ${tenantMethods.name || 'your organization'}`} />
|
|
217
|
+
{tenantMethods.openframe && (
|
|
218
|
+
// Not configured with anyone's credentials, so it carries no tag.
|
|
219
|
+
<SsoProviderButtons
|
|
220
|
+
providers={['openframe']}
|
|
221
|
+
onSsoClick={onSsoClick}
|
|
222
|
+
actionLabel={ssoActionLabel}
|
|
223
|
+
disabled={providersDisabled}
|
|
224
|
+
/>
|
|
225
|
+
)}
|
|
226
|
+
{tenantMethods.providers.length > 0 && (
|
|
227
|
+
<SsoProviderButtons
|
|
228
|
+
providers={tenantMethods.providers}
|
|
229
|
+
onSsoClick={onSsoClick}
|
|
230
|
+
actionLabel={ssoActionLabel}
|
|
231
|
+
disabled={providersDisabled}
|
|
232
|
+
tag={tenantMethods.name}
|
|
233
|
+
/>
|
|
234
|
+
)}
|
|
235
|
+
</>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
{ssoProviders.length > 0 && (
|
|
239
|
+
<>
|
|
240
|
+
{/* Under an organization heading with nothing above it, a second divider would only
|
|
241
|
+
separate the heading from its own buttons. */}
|
|
242
|
+
{(!tenantMethods || tenantMethods.openframe || tenantMethods.providers.length > 0) && (
|
|
243
|
+
<LabeledDivider label={dividerLabel} />
|
|
244
|
+
)}
|
|
245
|
+
<SsoProviderButtons
|
|
246
|
+
providers={ssoProviders}
|
|
247
|
+
onSsoClick={onSsoClick}
|
|
248
|
+
actionLabel={ssoActionLabel}
|
|
249
|
+
disabled={providersDisabled}
|
|
250
|
+
enabledProviders={ssoEnabledProviders}
|
|
251
|
+
/>
|
|
252
|
+
</>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
)}
|
|
256
|
+
|
|
200
257
|
{termsUrl && privacyPolicyUrl && <LegalLinks termsUrl={termsUrl} privacyPolicyUrl={privacyPolicyUrl} />}
|
|
201
258
|
</div>
|
|
202
259
|
);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { requireIcon, type IconComponent } from '../../chat/utils/icon-loading';
|
|
4
4
|
import { Button } from '../../ui/button';
|
|
5
|
+
import { StatusBadge } from '../../ui/status-badge';
|
|
5
6
|
|
|
6
7
|
/** SSO providers offered on the auth forms. */
|
|
7
8
|
export type AuthSsoProvider = 'openframe' | 'google' | 'microsoft' | 'apple';
|
|
@@ -34,6 +35,12 @@ export interface SsoProviderButtonsProps {
|
|
|
34
35
|
* `disabled` still wins over this list.
|
|
35
36
|
*/
|
|
36
37
|
enabledProviders?: AuthSsoProvider[];
|
|
38
|
+
/**
|
|
39
|
+
* A stamp on every button naming who configured these providers — e.g. the organization whose
|
|
40
|
+
* own Google Workspace a "Continue with Google" button signs in through, which is not the same
|
|
41
|
+
* button as the generic one.
|
|
42
|
+
*/
|
|
43
|
+
tag?: string;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
/** Stacked full-width SSO provider buttons shared by the auth forms. */
|
|
@@ -43,6 +50,7 @@ export function SsoProviderButtons({
|
|
|
43
50
|
actionLabel = 'Continue with',
|
|
44
51
|
disabled = false,
|
|
45
52
|
enabledProviders,
|
|
53
|
+
tag,
|
|
46
54
|
}: SsoProviderButtonsProps) {
|
|
47
55
|
return (
|
|
48
56
|
<div className="flex flex-col gap-[var(--spacing-system-l)]">
|
|
@@ -62,6 +70,19 @@ export function SsoProviderButtons({
|
|
|
62
70
|
onClick={() => onSsoClick?.(provider)}
|
|
63
71
|
>
|
|
64
72
|
{`${actionLabel} ${meta.name}`}
|
|
73
|
+
{tag && (
|
|
74
|
+
// A child, not `rightIcon`: the Button lays its children out through a `contents` span,
|
|
75
|
+
// so the badge is a flex item of the button itself and can give up width (`min-w-0`)
|
|
76
|
+
// where a long organization name would otherwise push the label out of a phone-width
|
|
77
|
+
// button. `block` over its inline-flex, because an ellipsis only renders on a block box.
|
|
78
|
+
<StatusBadge
|
|
79
|
+
text={tag}
|
|
80
|
+
variant="button"
|
|
81
|
+
singleLine
|
|
82
|
+
title={tag}
|
|
83
|
+
className="block min-w-0 max-w-32 truncate"
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
65
86
|
</Button>
|
|
66
87
|
);
|
|
67
88
|
})}
|