@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.
@@ -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 || 'Login 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: () => {
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: 'Login to Datalayer',
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: 'Login to Datalayer',
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: 'Login to Datalayer',
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: 'Login to Datalayer',
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: 'Login to Datalayer',
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, colormoded: 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, Flash, FormControl, Label, Spinner, Text, } from '@primer/react';
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(Flash, { variant: "default", children: flashMessage }) }), _jsx(ActionList, { selectionVariant: "single", children: isLoading ? (_jsx(ActionList.Item, { disabled: true, children: _jsxs(Box, { sx: {
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
- return _jsx(ExternalTokenSilentLoginRoute, { ...props });
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 Runtimes',
38
- icon: 'https://raw.githubusercontent.com/datalayer/icons/main/svg/data1/jupyter-base.svg',
37
+ name: 'Datalayer',
38
+ icon: null,
39
39
  rank: 1,
40
40
  },
41
41
  brand: {
@@ -1,9 +1,3 @@
1
- /**
2
- * Lightweight JWT payload utilities.
3
- *
4
- * Decodes a JWT without verification (client-side display only).
5
- * Never use for security-critical checks.
6
- */
7
1
  export interface DatalayerJwtUser {
8
2
  id: string;
9
3
  uid: string;
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 = `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim();
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 headingText = name ?? title;
73
- const headingIcon = icon ?? leadingIcon;
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 (_jsx(Box, { sx: {
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
- width: '100%',
293
- maxWidth: 440,
294
- p: 0,
295
- }, children: [!hideHero && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
296
- display: 'flex',
297
- alignItems: 'center',
298
- gap: 2,
299
- mb: 3,
300
- justifyContent: 'center',
301
- }, children: [headingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: headingText })] }), _jsx(Text, { as: "p", sx: {
302
- fontSize: 1,
303
- color: 'fg.muted',
304
- mb: 3,
305
- textAlign: 'center',
306
- }, 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'
307
- ? () => _jsx(Spinner, { size: "small" })
308
- : GithubMarkIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.github), sx: {
309
- mt: socialButtonMarginTop,
310
- maxWidth: actionButtonMaxWidth,
311
- mx: 'auto',
312
- }, children: "Sign in with GitHub" })), google && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'google'
313
- ? () => _jsx(Spinner, { size: "small" })
314
- : GoogleIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.google), sx: {
315
- mt: socialButtonMarginTop,
316
- maxWidth: actionButtonMaxWidth,
317
- mx: 'auto',
318
- }, children: "Sign in with Google" })), linkedin && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'linkedin'
319
- ? () => _jsx(Spinner, { size: "small" })
320
- : LinkedInGreyIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.linkedin), sx: {
321
- mt: socialButtonMarginTop,
322
- maxWidth: actionButtonMaxWidth,
323
- mx: 'auto',
324
- }, 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: {
325
- display: 'flex',
326
- alignItems: 'center',
327
- justifyContent: 'space-between',
328
- mt: 3,
329
- }, children: [_jsx(Button, { variant: "primary", disabled: loading || asDoc || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign in' }), showForgotPassword && (_jsx(Link, { as: "button", sx: {
330
- fontSize: 1,
331
- cursor: asDoc ? 'not-allowed' : 'pointer',
332
- background: 'none',
333
- border: 'none',
334
- opacity: asDoc ? 0.6 : 1,
335
- }, onClick: () => {
336
- if (asDoc || loading) {
337
- return;
338
- }
339
- handleForgotPassword();
340
- }, 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: {
341
- mt: 4,
342
- pt: 4,
343
- textAlign: 'center',
344
- borderTop: '1px solid',
345
- borderColor: 'border.muted',
346
- }, 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", disabled: asDoc || loading, onClick: handleSignUp, children: signUpLabel }) })] })), apiKey && onApiKeySignIn && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
347
- display: 'flex',
348
- alignItems: 'center',
349
- gap: 2,
350
- my: 3,
351
- }, 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 => {
352
- if (event.key === 'Escape') {
353
- closeApiKeyDialog();
354
- }
355
- }, onClick: closeApiKeyDialog, sx: {
356
- position: 'fixed',
357
- inset: 0,
358
- zIndex: 200,
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
- borderRadius: 2,
371
- border: '1px solid',
372
- borderColor: 'border.default',
373
- bg: 'canvas.overlay',
374
- color: 'fg.default',
375
- boxShadow: 'shadow.large',
376
- }, 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: {
377
- mt: 3,
378
- display: 'flex',
379
- justifyContent: 'flex-end',
380
- gap: 2,
381
- }, children: [_jsx(Button, { type: "button", onClick: closeApiKeyDialog, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: !apiKeyValue.trim(), children: "Authenticate" })] })] }) }))] }))] }) }));
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.subtle',
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: "JWT Claims" }), 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: {
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.49",
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.0.1",
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 {};
@@ -1,5 +0,0 @@
1
- /*
2
- * Copyright (c) 2023-2025 Datalayer, Inc.
3
- * Distributed under the terms of the Modified BSD License.
4
- */
5
- export {};