@datalayer/core 1.1.49 → 1.1.50
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/lib/components/auth/Login.js +1 -1
- package/lib/components/auth/Login.stories.js +5 -5
- package/lib/components/avatars/UserAvatar.d.ts +5 -1
- package/lib/components/avatars/UserAvatar.js +4 -4
- package/lib/components/billing/BillingEntitySelect.js +10 -2
- package/lib/components/iam/ExternalTokenSilentLogin.js +6 -2
- package/lib/state/substates/CoreState.js +2 -2
- package/lib/utils/Jwt.d.ts +0 -6
- package/lib/utils/Jwt.js +8 -1
- package/lib/views/iam/SignInSimple.js +132 -97
- package/lib/views/profile/UserBadge.js +6 -3
- package/package.json +2 -2
- package/lib/components/tokens/index.d.ts +0 -1
- package/lib/components/tokens/index.js +0 -5
|
@@ -183,7 +183,7 @@ export const Login = (props) => {
|
|
|
183
183
|
: 'Your password may not be empty.',
|
|
184
184
|
});
|
|
185
185
|
}, [formValues]);
|
|
186
|
-
return (_jsxs(PageLayout, { containerWidth: "medium", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsx(Heading, { children: heading || '
|
|
186
|
+
return (_jsxs(PageLayout, { containerWidth: "medium", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsx(Heading, { children: heading || 'Sign in to Datalayer' }) }), _jsx(PageLayout.Content, { children: loadingWithToken < 0 ? (_jsx(_Fragment, { children: _jsxs(Box, { display: "flex", children: [showEmailLogin && (_jsxs(Box, { sx: { label: { marginTop: 2 }, paddingRight: '10%' }, children: [_jsx(Box, { mt: 5, children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your username" }), _jsx(TextInput, { autoFocus: true, placeholder: "Your username", value: formValues.handle, disabled: loading, onChange: handleHandleChange, onKeyDown: handleKeyDown }), validationResult.handle && (_jsx(FormControl.Validation, { variant: validationResult.handle ? 'error' : 'success', children: validationResult.handle }))] }) }), _jsx(Box, { children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your password" }), _jsx(TextInput, { placeholder: "Your password", type: passwordVisibility ? 'text' : 'password', value: formValues.password, disabled: loading, onChange: handlePasswordChange, onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => {
|
|
187
187
|
setPasswordVisibility(!passwordVisibility);
|
|
188
188
|
}, disabled: loading, icon: passwordVisibility ? EyeClosedIcon : EyeIcon, "aria-label": passwordVisibility
|
|
189
189
|
? 'Hide password'
|
|
@@ -28,7 +28,7 @@ export default meta;
|
|
|
28
28
|
*/
|
|
29
29
|
export const Default = {
|
|
30
30
|
args: {
|
|
31
|
-
heading: '
|
|
31
|
+
heading: 'Sign in to Datalayer',
|
|
32
32
|
homeRoute: '/home',
|
|
33
33
|
loginRoute: '/login',
|
|
34
34
|
showEmailLogin: true,
|
|
@@ -41,7 +41,7 @@ export const Default = {
|
|
|
41
41
|
*/
|
|
42
42
|
export const EmailOnly = {
|
|
43
43
|
args: {
|
|
44
|
-
heading: '
|
|
44
|
+
heading: 'Sign in to Datalayer',
|
|
45
45
|
homeRoute: '/home',
|
|
46
46
|
loginRoute: '/login',
|
|
47
47
|
showEmailLogin: true,
|
|
@@ -54,7 +54,7 @@ export const EmailOnly = {
|
|
|
54
54
|
*/
|
|
55
55
|
export const OAuthOnly = {
|
|
56
56
|
args: {
|
|
57
|
-
heading: '
|
|
57
|
+
heading: 'Sign in to Datalayer',
|
|
58
58
|
homeRoute: '/home',
|
|
59
59
|
loginRoute: '/login',
|
|
60
60
|
showEmailLogin: false,
|
|
@@ -67,7 +67,7 @@ export const OAuthOnly = {
|
|
|
67
67
|
*/
|
|
68
68
|
export const TokenOnly = {
|
|
69
69
|
args: {
|
|
70
|
-
heading: '
|
|
70
|
+
heading: 'Sign in to Datalayer',
|
|
71
71
|
homeRoute: '/home',
|
|
72
72
|
loginRoute: '/login',
|
|
73
73
|
showEmailLogin: false,
|
|
@@ -93,7 +93,7 @@ export const CustomHeading = {
|
|
|
93
93
|
*/
|
|
94
94
|
export const WithPasswordReset = {
|
|
95
95
|
args: {
|
|
96
|
-
heading: '
|
|
96
|
+
heading: 'Sign in to Datalayer',
|
|
97
97
|
homeRoute: '/home',
|
|
98
98
|
loginRoute: '/login',
|
|
99
99
|
passwordRoute: '/password-reset',
|
|
@@ -11,6 +11,10 @@ export type UserAvatarProps = {
|
|
|
11
11
|
square?: boolean;
|
|
12
12
|
/** Fallback icon size. Defaults to ~48% of `size`. */
|
|
13
13
|
iconSize?: number;
|
|
14
|
+
/** Optional background color override for the default (non-photo) avatar. */
|
|
15
|
+
fallbackBackground?: string;
|
|
16
|
+
/** Optional icon foreground color override for the default avatar. */
|
|
17
|
+
fallbackForeground?: string;
|
|
14
18
|
};
|
|
15
|
-
export declare const UserAvatar: ({ avatarUrl, size, square, iconSize, }: UserAvatarProps) => JSX.Element;
|
|
19
|
+
export declare const UserAvatar: ({ avatarUrl, size, square, iconSize, fallbackBackground, fallbackForeground, }: UserAvatarProps) => JSX.Element;
|
|
16
20
|
export default UserAvatar;
|
|
@@ -27,7 +27,7 @@ export function hasRealAvatar(url) {
|
|
|
27
27
|
}
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
30
|
-
export const UserAvatar = ({ avatarUrl, size = 100, square = true, iconSize, }) => {
|
|
30
|
+
export const UserAvatar = ({ avatarUrl, size = 100, square = true, iconSize, fallbackBackground, fallbackForeground, }) => {
|
|
31
31
|
const palette = useColorPalette();
|
|
32
32
|
if (hasRealAvatar(avatarUrl)) {
|
|
33
33
|
return _jsx(DLAvatar, { square: square, src: avatarUrl, size: size });
|
|
@@ -37,11 +37,11 @@ export const UserAvatar = ({ avatarUrl, size = 100, square = true, iconSize, })
|
|
|
37
37
|
width: size,
|
|
38
38
|
height: size,
|
|
39
39
|
borderRadius: square ? 2 : '50%',
|
|
40
|
-
bg: 'accent.subtle',
|
|
40
|
+
bg: fallbackBackground || 'accent.subtle',
|
|
41
41
|
display: 'flex',
|
|
42
42
|
alignItems: 'center',
|
|
43
43
|
justifyContent: 'center',
|
|
44
|
-
'--datalayer-icon-fg': palette.primary,
|
|
45
|
-
}, children: _jsx(AlienIcon, { size: resolvedIconSize, themed: true,
|
|
44
|
+
'--datalayer-icon-fg': fallbackForeground || palette.primary,
|
|
45
|
+
}, children: _jsx(AlienIcon, { size: resolvedIconSize, themed: true, colormode: true }) }));
|
|
46
46
|
};
|
|
47
47
|
export default UserAvatar;
|
|
@@ -17,7 +17,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
17
17
|
* resolved account via `onSelectedAccountChange`.
|
|
18
18
|
*/
|
|
19
19
|
import { useCallback, useEffect, useMemo, useRef, Fragment } from 'react';
|
|
20
|
-
import { ActionList, ActionMenu, Button,
|
|
20
|
+
import { ActionList, ActionMenu, Button, FormControl, Label, Spinner, Text, } from '@primer/react';
|
|
21
21
|
import { OrganizationIcon, PeopleIcon, PersonIcon, } from '@primer/octicons-react';
|
|
22
22
|
import { Box } from '@datalayer/primer-addons';
|
|
23
23
|
import { useCache } from '../../hooks/useCache.js';
|
|
@@ -324,7 +324,15 @@ export function BillingEntitySelect({ value, onChange, onSelectedAccountChange,
|
|
|
324
324
|
? 'Organization'
|
|
325
325
|
: selectedAccount.accountType === 'team'
|
|
326
326
|
? 'Team'
|
|
327
|
-
: 'User', children: selectedAccount.accountType === 'organization' ? (_jsx(OrganizationIcon, { size: 12 })) : selectedAccount.accountType === 'team' ? (_jsx(PeopleIcon, { size: 12 })) : (_jsx(PersonIcon, { size: 12 })) }), _jsx(Label, { size: "small", variant: selectedAccount.isPaidPlan ? 'success' : 'attention', children: formatBillingEntityPlanLabel(selectedAccount.planName) })] })] })) : !hasEligibleAccount ? (emptyMessage) : ('Select a billing entity') }) }), _jsxs(ActionMenu.Overlay, { width: "large", children: [_jsx(Box, { sx: { p: 2 }, children: _jsx(
|
|
327
|
+
: 'User', children: selectedAccount.accountType === 'organization' ? (_jsx(OrganizationIcon, { size: 12 })) : selectedAccount.accountType === 'team' ? (_jsx(PeopleIcon, { size: 12 })) : (_jsx(PersonIcon, { size: 12 })) }), _jsx(Label, { size: "small", variant: selectedAccount.isPaidPlan ? 'success' : 'attention', children: formatBillingEntityPlanLabel(selectedAccount.planName) })] })] })) : !hasEligibleAccount ? (emptyMessage) : ('Select a billing entity') }) }), _jsxs(ActionMenu.Overlay, { width: "large", children: [_jsx(Box, { sx: { p: 2 }, children: _jsx(Box, { sx: {
|
|
328
|
+
borderRadius: 2,
|
|
329
|
+
border: '1px solid',
|
|
330
|
+
borderColor: 'border.default',
|
|
331
|
+
bg: 'canvas.subtle',
|
|
332
|
+
color: 'fg.muted',
|
|
333
|
+
px: 3,
|
|
334
|
+
py: 2,
|
|
335
|
+
}, children: _jsx(Text, { sx: { fontSize: 1, lineHeight: 1.5 }, children: flashMessage }) }) }), _jsx(ActionList, { selectionVariant: "single", children: isLoading ? (_jsx(ActionList.Item, { disabled: true, children: _jsxs(Box, { sx: {
|
|
328
336
|
display: 'inline-flex',
|
|
329
337
|
alignItems: 'center',
|
|
330
338
|
gap: 2,
|
|
@@ -3,10 +3,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
|
-
import { useEffect } from 'react';
|
|
6
|
+
import { useContext, useEffect } from 'react';
|
|
7
|
+
import { QueryClient, QueryClientContext, QueryClientProvider, } from '@tanstack/react-query';
|
|
7
8
|
import { CenteredSpinner } from '../../components/display/index.js';
|
|
8
9
|
import { useIAMStore } from '../../state/index.js';
|
|
9
10
|
import { useToast, useIAM } from '../../hooks/index.js';
|
|
11
|
+
const externalTokenQueryClient = new QueryClient();
|
|
10
12
|
const ExternalTokenSilentLoginRoute = (props) => {
|
|
11
13
|
const { message } = props;
|
|
12
14
|
const { loginAndNavigate } = useIAM();
|
|
@@ -29,7 +31,9 @@ const ExternalTokenSilentLoginRoute = (props) => {
|
|
|
29
31
|
return _jsx(CenteredSpinner, { size: "small", message: message });
|
|
30
32
|
};
|
|
31
33
|
export const ExternalTokenSilentLogin = (props) => {
|
|
34
|
+
const queryClient = useContext(QueryClientContext);
|
|
32
35
|
// No navigation provider needed anymore - auto-detection works without it
|
|
33
|
-
|
|
36
|
+
const content = _jsx(ExternalTokenSilentLoginRoute, { ...props });
|
|
37
|
+
return queryClient ? (content) : (_jsx(QueryClientProvider, { client: externalTokenQueryClient, children: content }));
|
|
34
38
|
};
|
|
35
39
|
export default ExternalTokenSilentLogin;
|
|
@@ -34,8 +34,8 @@ let initialConfiguration = {
|
|
|
34
34
|
supportUrl: 'https://prod1.datalayer.run',
|
|
35
35
|
launcher: {
|
|
36
36
|
category: 'Datalayer',
|
|
37
|
-
name: 'Datalayer
|
|
38
|
-
icon:
|
|
37
|
+
name: 'Datalayer',
|
|
38
|
+
icon: null,
|
|
39
39
|
rank: 1,
|
|
40
40
|
},
|
|
41
41
|
brand: {
|
package/lib/utils/Jwt.d.ts
CHANGED
package/lib/utils/Jwt.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Lightweight JWT payload utilities.
|
|
7
|
+
*
|
|
8
|
+
* Decodes a JWT without verification (client-side display only).
|
|
9
|
+
* Never use for security-critical checks.
|
|
10
|
+
*/
|
|
11
|
+
import { asDisplayName } from './Name.js';
|
|
5
12
|
// ── Utilities ─────────────────────────────────────────────────────
|
|
6
13
|
/**
|
|
7
14
|
* Decode the payload of a JWT without verifying the signature.
|
|
@@ -39,6 +46,6 @@ export function getDatalayerJwtUser(token) {
|
|
|
39
46
|
export function getDatalayerDisplayName(user, fallback = '') {
|
|
40
47
|
if (!user)
|
|
41
48
|
return fallback;
|
|
42
|
-
const full =
|
|
49
|
+
const full = asDisplayName(user.firstName ?? '', user.lastName ?? '').trim();
|
|
43
50
|
return full || user.handle || fallback;
|
|
44
51
|
}
|
|
@@ -14,8 +14,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
14
14
|
*/
|
|
15
15
|
import { useState, useCallback, useRef, useMemo, useEffect, } from 'react';
|
|
16
16
|
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
17
|
+
import { createGlobalStyle } from 'styled-components';
|
|
17
18
|
import { Box, Button, FormControl, Heading, Link, Spinner, Text, Textarea, TextInput, } from '@primer/react';
|
|
18
|
-
import { EyeIcon, EyeClosedIcon, KeyIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
19
|
+
import { EyeIcon, EyeClosedIcon, KeyIcon, MailIcon, SignInIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
19
20
|
import { GithubMarkIcon, GoogleIcon, LinkedInGreyIcon, } from '@datalayer/icons-react';
|
|
20
21
|
import { useToast } from '../../hooks/index.js';
|
|
21
22
|
import { isInsideJupyterLab } from '../../utils/Jupyter.js';
|
|
@@ -67,10 +68,44 @@ const getErrorMessage = (error, fallback = 'Unable to start social sign-in.') =>
|
|
|
67
68
|
}
|
|
68
69
|
return fallback;
|
|
69
70
|
};
|
|
71
|
+
// Repaint the browser autofill highlight (Chrome's blue/yellow) with theme
|
|
72
|
+
// tokens so autofilled/disabled inputs keep the Primer surface colors. A
|
|
73
|
+
// scoped global style is used because Primer's TextInput inner <input> is not
|
|
74
|
+
// reliably reachable via the wrapper `sx` for vendor-prefixed autofill pseudos.
|
|
75
|
+
const SignInInputGlobalStyle = createGlobalStyle `
|
|
76
|
+
.signin-input-theme-scope input:-webkit-autofill,
|
|
77
|
+
.signin-input-theme-scope input:-webkit-autofill:hover,
|
|
78
|
+
.signin-input-theme-scope input:-webkit-autofill:focus,
|
|
79
|
+
.signin-input-theme-scope input:-webkit-autofill:active,
|
|
80
|
+
.signin-input-theme-scope input:-webkit-autofill:disabled,
|
|
81
|
+
.signin-input-theme-scope textarea:-webkit-autofill,
|
|
82
|
+
.signin-input-theme-scope textarea:-webkit-autofill:hover,
|
|
83
|
+
.signin-input-theme-scope textarea:-webkit-autofill:focus,
|
|
84
|
+
.signin-input-theme-scope textarea:-webkit-autofill:active,
|
|
85
|
+
.signin-input-theme-scope textarea:-webkit-autofill:disabled {
|
|
86
|
+
-webkit-text-fill-color: var(--fgColor-default) !important;
|
|
87
|
+
caret-color: var(--fgColor-default) !important;
|
|
88
|
+
-webkit-box-shadow: 0 0 0 1000px var(--bgColor-default) inset !important;
|
|
89
|
+
box-shadow: 0 0 0 1000px var(--bgColor-default) inset !important;
|
|
90
|
+
background-color: var(--bgColor-default) !important;
|
|
91
|
+
transition: background-color 9999s ease-in-out 0s !important;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.signin-input-theme-scope input:disabled,
|
|
95
|
+
.signin-input-theme-scope textarea:disabled {
|
|
96
|
+
-webkit-text-fill-color: var(--fgColor-muted) !important;
|
|
97
|
+
-webkit-box-shadow: 0 0 0 1000px var(--bgColor-default) inset !important;
|
|
98
|
+
box-shadow: 0 0 0 1000px var(--bgColor-default) inset !important;
|
|
99
|
+
background-color: var(--bgColor-default) !important;
|
|
100
|
+
opacity: 1 !important;
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
70
103
|
// ── Component ────────────────────────────────────────────────────────
|
|
71
104
|
export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp, name, title = 'Datalayer OTEL', description = 'Sign in to access the observability dashboard.', icon, leadingIcon = _jsx(TelescopeIcon, { size: 24 }), github = false, google = false, linkedin = false, apiKey = false, signUp = true, socialSignInNavigationTarget, asDoc = false, hideHero = false, calloutTitle, calloutDescription, passwordToggleLabel = 'Sign in with a password', signUpTitle = "Don't have an account?", signUpDescription = 'Create a free Datalayer account with your email address.', signUpLabel = 'Sign up with email', signUpHref = 'https://datalayer.ai/signup', forgotPasswordHref = '/password', forgotPasswordLabel = 'Forgot password?', showForgotPassword = true, socialButtonMarginTop = 3, actionButtonMaxWidth = 320, }) => {
|
|
72
|
-
const
|
|
73
|
-
const
|
|
105
|
+
const compactDocMode = asDoc && !hideHero;
|
|
106
|
+
const headingText = name ??
|
|
107
|
+
(asDoc && title === 'Datalayer OTEL' ? 'Datalayer Sign In' : title);
|
|
108
|
+
const headingIcon = icon ?? (asDoc ? _jsx(SignInIcon, { size: 24 }) : leadingIcon);
|
|
74
109
|
const loginUrl = useMemo(() => {
|
|
75
110
|
if (loginUrlProp)
|
|
76
111
|
return loginUrlProp;
|
|
@@ -280,104 +315,104 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
|
|
|
280
315
|
}
|
|
281
316
|
}, [mfaUserUid, submit, submitMfa]);
|
|
282
317
|
const socialDisabled = socialLoading || loading || asDoc;
|
|
283
|
-
return (
|
|
318
|
+
return (_jsxs(Box, { className: "signin-input-theme-scope", sx: {
|
|
284
319
|
display: 'flex',
|
|
285
|
-
alignItems: hideHero ? 'flex-start' : 'center',
|
|
320
|
+
alignItems: hideHero || compactDocMode ? 'flex-start' : 'center',
|
|
286
321
|
justifyContent: 'center',
|
|
287
|
-
minHeight: hideHero ? 'auto' : '100vh',
|
|
322
|
+
minHeight: hideHero || compactDocMode ? 'auto' : '100vh',
|
|
288
323
|
bg: hideHero ? 'transparent' : 'canvas.default',
|
|
289
324
|
color: 'fg.default',
|
|
290
|
-
py: hideHero ? 0 : 4,
|
|
291
|
-
}, children: _jsxs(Box, { sx: {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
p: 3,
|
|
360
|
-
bg: 'canvas.backdrop',
|
|
361
|
-
display: 'flex',
|
|
362
|
-
alignItems: 'center',
|
|
363
|
-
justifyContent: 'center',
|
|
364
|
-
}, children: _jsxs(Box, { as: "form", onClick: event => event.stopPropagation(), onSubmit: event => {
|
|
365
|
-
event.preventDefault();
|
|
366
|
-
handleApiKeyAuthenticate();
|
|
367
|
-
}, sx: {
|
|
368
|
-
width: 'min(560px, 100%)',
|
|
325
|
+
py: hideHero ? 0 : compactDocMode ? 2 : 4,
|
|
326
|
+
}, children: [_jsx(SignInInputGlobalStyle, {}), _jsxs(Box, { sx: {
|
|
327
|
+
width: '100%',
|
|
328
|
+
maxWidth: 440,
|
|
329
|
+
p: 0,
|
|
330
|
+
}, children: [!hideHero && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
331
|
+
display: 'flex',
|
|
332
|
+
alignItems: 'center',
|
|
333
|
+
gap: 2,
|
|
334
|
+
mb: compactDocMode ? 2 : 3,
|
|
335
|
+
justifyContent: 'center',
|
|
336
|
+
}, children: [headingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: headingText })] }), _jsx(Text, { as: "p", sx: {
|
|
337
|
+
fontSize: 1,
|
|
338
|
+
color: 'fg.muted',
|
|
339
|
+
mb: compactDocMode ? 2 : 3,
|
|
340
|
+
textAlign: 'center',
|
|
341
|
+
}, children: description })] })), (calloutTitle || calloutDescription) && (_jsxs(Box, { sx: { mb: 3, textAlign: 'center' }, children: [calloutTitle && (_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: calloutTitle })), calloutDescription && (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: calloutDescription }))] })), !mfaUserUid && (github || google || linkedin) && (_jsxs(_Fragment, { children: [github && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'github'
|
|
342
|
+
? () => _jsx(Spinner, { size: "small" })
|
|
343
|
+
: GithubMarkIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.github), sx: {
|
|
344
|
+
mt: socialButtonMarginTop,
|
|
345
|
+
maxWidth: actionButtonMaxWidth,
|
|
346
|
+
mx: 'auto',
|
|
347
|
+
}, children: "Sign in with GitHub" })), google && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'google'
|
|
348
|
+
? () => _jsx(Spinner, { size: "small" })
|
|
349
|
+
: GoogleIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.google), sx: {
|
|
350
|
+
mt: socialButtonMarginTop,
|
|
351
|
+
maxWidth: actionButtonMaxWidth,
|
|
352
|
+
mx: 'auto',
|
|
353
|
+
}, children: "Sign in with Google" })), linkedin && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'linkedin'
|
|
354
|
+
? () => _jsx(Spinner, { size: "small" })
|
|
355
|
+
: LinkedInGreyIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.linkedin), sx: {
|
|
356
|
+
mt: socialButtonMarginTop,
|
|
357
|
+
maxWidth: actionButtonMaxWidth,
|
|
358
|
+
mx: 'auto',
|
|
359
|
+
}, children: "Sign in with LinkedIn" }))] })), !mfaUserUid && !showPasswordForm && (_jsx(Box, { sx: { mt: 3, display: 'flex', justifyContent: 'center' }, children: _jsx(Button, { leadingVisual: KeyIcon, size: "large", block: true, disabled: asDoc || loading || socialLoading, onClick: () => setShowPasswordForm(true), sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: passwordToggleLabel }) })), !mfaUserUid && showPasswordForm && (_jsxs(Box, { sx: { mt: 4 }, children: [_jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Username" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your username", value: handle, disabled: loading || asDoc, onChange: e => setHandle(e.target.value), onKeyDown: handleKeyDown })] }), _jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Password" }), _jsx(TextInput, { block: true, placeholder: "Your password", type: showPassword ? 'text' : 'password', value: password, disabled: loading || asDoc, onChange: e => setPassword(e.target.value), onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => setShowPassword(!showPassword), disabled: loading || asDoc, icon: showPassword ? EyeClosedIcon : EyeIcon, "aria-label": showPassword ? 'Hide password' : 'Show password', sx: { color: 'var(--fgColor-muted)' } }) })] }), _jsxs(Box, { sx: {
|
|
360
|
+
display: 'flex',
|
|
361
|
+
alignItems: 'center',
|
|
362
|
+
justifyContent: 'space-between',
|
|
363
|
+
mt: 3,
|
|
364
|
+
}, children: [_jsx(Button, { variant: "primary", disabled: loading || asDoc || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign in' }), showForgotPassword && (_jsx(Link, { as: "button", sx: {
|
|
365
|
+
fontSize: 1,
|
|
366
|
+
cursor: asDoc ? 'not-allowed' : 'pointer',
|
|
367
|
+
background: 'none',
|
|
368
|
+
border: 'none',
|
|
369
|
+
opacity: asDoc ? 0.6 : 1,
|
|
370
|
+
}, onClick: () => {
|
|
371
|
+
if (asDoc || loading) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
handleForgotPassword();
|
|
375
|
+
}, children: forgotPasswordLabel }))] })] })), mfaUserUid && (_jsxs(_Fragment, { children: [_jsxs(FormControl, { required: true, sx: { mb: 3, mt: 2 }, children: [_jsx(FormControl.Label, { children: "MFA Code" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your MFA code", value: mfaCode, disabled: loading || asDoc, onChange: e => setMfaCode(e.target.value), onKeyDown: handleKeyDown })] }), _jsx(Button, { variant: "primary", block: true, disabled: loading || asDoc || !mfaCode.trim(), onClick: submitMfa, sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: loading ? 'Validating…' : 'Validate MFA Code' })] })), error && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 1, mt: 3, display: 'block' }, children: error })), signUp && (_jsxs(Box, { sx: {
|
|
376
|
+
mt: 4,
|
|
377
|
+
pt: 4,
|
|
378
|
+
textAlign: 'center',
|
|
379
|
+
borderTop: '1px solid',
|
|
380
|
+
borderColor: 'border.muted',
|
|
381
|
+
}, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: signUpTitle }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mb: 3 }, children: signUpDescription }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'center' }, children: _jsx(Button, { variant: "primary", size: "large", leadingVisual: MailIcon, disabled: asDoc || loading, onClick: handleSignUp, children: signUpLabel }) })] })), apiKey && onApiKeySignIn && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
382
|
+
display: 'flex',
|
|
383
|
+
alignItems: 'center',
|
|
384
|
+
gap: 2,
|
|
385
|
+
my: 3,
|
|
386
|
+
}, children: [_jsx(Box, { sx: { flex: 1, height: '1px', bg: 'border.default' } }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "or" }), _jsx(Box, { sx: { flex: 1, height: '1px', bg: 'border.default' } })] }), _jsx(Button, { block: true, leadingVisual: KeyIcon, disabled: asDoc || loading || socialLoading, onClick: () => setShowApiKeyDialog(true), sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: "Sign In with an API Key" }), showApiKeyDialog && (_jsx(Box, { role: "dialog", "aria-modal": "true", "aria-labelledby": "signin-api-key-title", onKeyDown: event => {
|
|
387
|
+
if (event.key === 'Escape') {
|
|
388
|
+
closeApiKeyDialog();
|
|
389
|
+
}
|
|
390
|
+
}, onClick: closeApiKeyDialog, sx: {
|
|
391
|
+
position: 'fixed',
|
|
392
|
+
inset: 0,
|
|
393
|
+
zIndex: 200,
|
|
369
394
|
p: 3,
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
395
|
+
bg: 'canvas.backdrop',
|
|
396
|
+
display: 'flex',
|
|
397
|
+
alignItems: 'center',
|
|
398
|
+
justifyContent: 'center',
|
|
399
|
+
}, children: _jsxs(Box, { as: "form", onClick: event => event.stopPropagation(), onSubmit: event => {
|
|
400
|
+
event.preventDefault();
|
|
401
|
+
handleApiKeyAuthenticate();
|
|
402
|
+
}, sx: {
|
|
403
|
+
width: 'min(560px, 100%)',
|
|
404
|
+
p: 3,
|
|
405
|
+
borderRadius: 2,
|
|
406
|
+
border: '1px solid',
|
|
407
|
+
borderColor: 'border.default',
|
|
408
|
+
bg: 'canvas.overlay',
|
|
409
|
+
color: 'fg.default',
|
|
410
|
+
boxShadow: 'shadow.large',
|
|
411
|
+
}, children: [_jsx(Heading, { id: "signin-api-key-title", sx: { fontSize: 2, mb: 2 }, children: "Enter your API Key" }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "API Key" }), _jsx(Textarea, { block: true, required: true, autoFocus: true, placeholder: "Paste your API key here", value: apiKeyValue, onInput: (e) => setApiKeyValue(e.target.value), ref: apiKeyRef })] }), _jsxs(Box, { sx: {
|
|
412
|
+
mt: 3,
|
|
413
|
+
display: 'flex',
|
|
414
|
+
justifyContent: 'flex-end',
|
|
415
|
+
gap: 2,
|
|
416
|
+
}, children: [_jsx(Button, { type: "button", onClick: closeApiKeyDialog, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: !apiKeyValue.trim(), children: "Authenticate" })] })] }) }))] }))] })] }));
|
|
382
417
|
};
|
|
383
418
|
export default SignInSimple;
|
|
@@ -36,6 +36,9 @@ export const UserBadge = ({ token, variant = 'full', showExpandToggle = true, ex
|
|
|
36
36
|
const user = getDatalayerJwtUser(token);
|
|
37
37
|
const displayName = getDatalayerDisplayName(user, user?.handle ?? '');
|
|
38
38
|
const claims = parseJwtPayload(token);
|
|
39
|
+
const popoverDisplayName = claims?.user
|
|
40
|
+
? getDatalayerDisplayName(claims.user, claims.user.handle ?? '')
|
|
41
|
+
: displayName;
|
|
39
42
|
// Colour the trigger label based on token expiry:
|
|
40
43
|
// - red when already expired,
|
|
41
44
|
// - yellow when expiring within ``expirationWarningMs``,
|
|
@@ -84,14 +87,14 @@ export const UserBadge = ({ token, variant = 'full', showExpandToggle = true, ex
|
|
|
84
87
|
}, children: [_jsxs(Box, { sx: {
|
|
85
88
|
px: 3,
|
|
86
89
|
py: 2,
|
|
87
|
-
bg: 'canvas.
|
|
90
|
+
bg: 'canvas.inset',
|
|
88
91
|
borderBottom: '1px solid',
|
|
89
92
|
borderColor: 'border.default',
|
|
90
93
|
display: 'flex',
|
|
91
94
|
alignItems: 'center',
|
|
92
95
|
justifyContent: 'space-between',
|
|
93
96
|
gap: 2,
|
|
94
|
-
}, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children:
|
|
97
|
+
}, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: popoverDisplayName || 'User' }), variant === 'small' && showExpandToggle && (_jsx(Box, { as: "button", type: "button", onClick: () => setExpanded(v => !v), "aria-label": expanded ? 'Hide full JWT claims' : 'Show full JWT claims', "aria-expanded": expanded, title: expanded ? 'Hide full JWT claims' : 'Show full JWT claims', sx: {
|
|
95
98
|
appearance: 'none',
|
|
96
99
|
border: '1px solid',
|
|
97
100
|
borderColor: 'border.default',
|
|
@@ -120,7 +123,7 @@ export const UserBadge = ({ token, variant = 'full', showExpandToggle = true, ex
|
|
|
120
123
|
gridTemplateColumns: '90px 1fr',
|
|
121
124
|
gap: 1,
|
|
122
125
|
fontSize: 0,
|
|
123
|
-
}, children: [claims.user.email && (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Email" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.email })] })), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Handle" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.handle }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "UID" }), _jsx(Text, { sx: { fontFamily: 'mono', wordBreak: 'break-all' }, children: claims.user.uid }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Issued" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: new Date(claims.iat * 1000).toISOString() }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Expires" }), _jsxs(Box, { sx: {
|
|
126
|
+
}, children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "First name" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.firstName }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Last name" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.lastName }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Display name" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: getDatalayerDisplayName(claims.user, claims.user.handle) }), claims.user.email && (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Email" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.email })] })), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Handle" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.handle }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "UID" }), _jsx(Text, { sx: { fontFamily: 'mono', wordBreak: 'break-all' }, children: claims.user.uid }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Issued" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: new Date(claims.iat * 1000).toISOString() }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Expires" }), _jsxs(Box, { sx: {
|
|
124
127
|
display: 'flex',
|
|
125
128
|
alignItems: 'center',
|
|
126
129
|
gap: 2,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalayer/core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.50",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
"playwright": "^1.53.2",
|
|
225
225
|
"prettier": "^3.6.2",
|
|
226
226
|
"react-router-dom": "^6.22.3",
|
|
227
|
-
"rimraf": "^6.
|
|
227
|
+
"rimraf": "^6.1.3",
|
|
228
228
|
"storybook": "^9.1.1",
|
|
229
229
|
"typedoc-plugin-markdown": "^4.0.0",
|
|
230
230
|
"typescript": "^5.8.3",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|