@datalayer/core 1.1.49 → 1.1.56

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.
Files changed (82) hide show
  1. package/README.md +19 -42
  2. package/lib/client/auth/AuthenticationManager.js +6 -4
  3. package/lib/client/auth/strategies.js +2 -0
  4. package/lib/components/animation/AnimatedText.d.ts +98 -0
  5. package/lib/components/animation/AnimatedText.js +220 -0
  6. package/lib/components/animation/index.d.ts +1 -0
  7. package/lib/components/{tokens → animation}/index.js +1 -1
  8. package/lib/components/auth/{LoginToken.d.ts → SigInAPIKey.d.ts} +3 -3
  9. package/lib/components/auth/{LoginToken.js → SigInAPIKey.js} +5 -5
  10. package/lib/components/auth/{Login.d.ts → SignIn.d.ts} +3 -3
  11. package/lib/components/auth/{Login.js → SignIn.js} +13 -13
  12. package/lib/components/auth/{Login.stories.d.ts → SignIn.stories.d.ts} +2 -2
  13. package/lib/components/auth/{Login.stories.js → SignIn.stories.js} +9 -9
  14. package/lib/components/auth/index.d.ts +2 -2
  15. package/lib/components/auth/index.js +2 -2
  16. package/lib/components/avatars/UserAvatar.d.ts +7 -1
  17. package/lib/components/avatars/UserAvatar.js +19 -6
  18. package/lib/components/avatars/UserProfileAvatar.js +2 -6
  19. package/lib/components/billing/BillingEntitySelect.js +10 -2
  20. package/lib/components/collaboration/LiveEditorCollaborators.d.ts +31 -0
  21. package/lib/components/collaboration/LiveEditorCollaborators.js +107 -0
  22. package/lib/components/collaboration/index.d.ts +1 -0
  23. package/lib/components/collaboration/index.js +9 -0
  24. package/lib/components/display/DatalayerBox.d.ts +3 -1
  25. package/lib/components/display/DatalayerBox.js +7 -7
  26. package/lib/components/display/JupyterDialog.d.ts +2 -0
  27. package/lib/components/display/JupyterDialog.js +10 -4
  28. package/lib/components/iam/ExternalTokenSilentLogin.js +8 -4
  29. package/lib/components/index.d.ts +2 -0
  30. package/lib/components/index.js +2 -0
  31. package/lib/components/labels/StatusLabels.d.ts +42 -0
  32. package/lib/components/labels/StatusLabels.js +61 -0
  33. package/lib/components/labels/index.d.ts +1 -0
  34. package/lib/components/labels/index.js +1 -0
  35. package/lib/components/principal/Principal.d.ts +2 -0
  36. package/lib/components/principal/Principal.js +11 -1
  37. package/lib/components/principal/PrincipalAppearance.d.ts +136 -0
  38. package/lib/components/principal/PrincipalAppearance.js +203 -0
  39. package/lib/components/principal/PrincipalAvatar.d.ts +5 -2
  40. package/lib/components/principal/PrincipalAvatar.js +16 -3
  41. package/lib/components/principal/PrincipalBadge.js +7 -0
  42. package/lib/components/principal/PrincipalDetailsOverlay.d.ts +2 -1
  43. package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
  44. package/lib/components/principal/index.d.ts +1 -0
  45. package/lib/components/principal/index.js +1 -0
  46. package/lib/components/progress/ConsumptionBar.d.ts +9 -0
  47. package/lib/components/progress/ConsumptionBar.js +10 -2
  48. package/lib/components/sharing/ShareAccessComponent.js +6 -1
  49. package/lib/config/Configuration.d.ts +17 -8
  50. package/lib/config/Configuration.js +12 -5
  51. package/lib/hooks/useCache.d.ts +2 -0
  52. package/lib/hooks/useCache.js +35 -11
  53. package/lib/hooks/usePrincipalCacheStore.d.ts +2 -0
  54. package/lib/hooks/usePrincipalCacheStore.js +2 -0
  55. package/lib/models/IAM.d.ts +10 -0
  56. package/lib/models/Organization.d.ts +2 -0
  57. package/lib/models/Organization.js +2 -0
  58. package/lib/models/Profile.d.ts +2 -0
  59. package/lib/models/Team.d.ts +2 -0
  60. package/lib/models/Team.js +2 -0
  61. package/lib/models/User.d.ts +4 -0
  62. package/lib/models/User.js +4 -0
  63. package/lib/models/UserDTO.d.ts +6 -0
  64. package/lib/models/UserDTO.js +8 -0
  65. package/lib/state/substates/CoreState.js +5 -4
  66. package/lib/state/substates/IAMState.js +6 -0
  67. package/lib/state/substates/LayoutState.js +62 -7
  68. package/lib/state/substates/OrganizationState.d.ts +6 -0
  69. package/lib/state/substates/OrganizationState.js +5 -0
  70. package/lib/state/substates/ProfileState.d.ts +34 -0
  71. package/lib/state/substates/ProfileState.js +68 -0
  72. package/lib/state/substates/TeamState.d.ts +6 -0
  73. package/lib/state/substates/TeamState.js +3 -0
  74. package/lib/state/substates/index.d.ts +1 -0
  75. package/lib/state/substates/index.js +1 -0
  76. package/lib/utils/Jwt.d.ts +0 -6
  77. package/lib/utils/Jwt.js +8 -1
  78. package/lib/views/iam/SignInSimple.d.ts +1 -1
  79. package/lib/views/iam/SignInSimple.js +131 -97
  80. package/lib/views/profile/UserBadge.js +6 -3
  81. package/package.json +3 -3
  82. package/lib/components/tokens/index.d.ts +0 -1
@@ -0,0 +1,68 @@
1
+ /*
2
+ * Copyright (c) 2023-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * ProfileState — the one place the authenticated user's profile lives.
7
+ *
8
+ * The avatar and the banner used to be read from whatever carried them at
9
+ * each call site — the IAM user, a whoami response, a form snapshot — and
10
+ * the surfaces drifted: a banner updated on the profile page did not reach
11
+ * the user menu until the page reloaded. Every surface showing the profile
12
+ * (user menu, sidebars, profile views) reads this store instead, and every
13
+ * flow that learns something about the profile (login, `GET /me`, a profile
14
+ * update) writes it here.
15
+ *
16
+ * The store is fed automatically by {@link IAMState} on login, profile
17
+ * update and logout, and by the `useMe` query with the full profile — the
18
+ * consumers only ever read.
19
+ *
20
+ * @module state/substates/ProfileState
21
+ */
22
+ import { useStore } from 'zustand';
23
+ import { createStore } from 'zustand/vanilla';
24
+ const userToProfile = (user) => user
25
+ ? {
26
+ id: user.id,
27
+ uid: user.uid,
28
+ handle: user.handle,
29
+ email: user.email,
30
+ firstName: user.firstName,
31
+ lastName: user.lastName,
32
+ displayName: user.displayName,
33
+ initials: user.initials,
34
+ avatarUrl: user.avatarUrl,
35
+ avatarIcon: user.avatarIcon,
36
+ banner: user.banner,
37
+ origin: user.origin,
38
+ roles: user.roles,
39
+ }
40
+ : undefined;
41
+ export const profileStore = createStore(set => ({
42
+ profile: undefined,
43
+ setProfile: profile => set({ profile }),
44
+ updateProfile: partial => set(state => ({ profile: { ...state.profile, ...partial } })),
45
+ setProfileFromUser: user => set(state => {
46
+ const profile = userToProfile(user);
47
+ if (!profile) {
48
+ return state;
49
+ }
50
+ // A login refresh may know less than a profile update that already
51
+ // landed (the anonymous whoami has no banner): merge, newest wins,
52
+ // but never erase a field with undefined.
53
+ const merged = { ...state.profile };
54
+ for (const [key, value] of Object.entries(profile)) {
55
+ if (value !== undefined) {
56
+ merged[key] = value;
57
+ }
58
+ }
59
+ return { profile: merged };
60
+ }),
61
+ clearProfile: () => set({ profile: undefined }),
62
+ }));
63
+ export function useProfileStore(selector) {
64
+ return useStore(profileStore, selector);
65
+ }
66
+ /** The profile, for surfaces that show it. */
67
+ export const useProfile = () => useProfileStore(state => state.profile);
68
+ export default useProfileStore;
@@ -4,6 +4,12 @@ export type ITeamState = {
4
4
  };
5
5
  export type TeamState = ITeamState & {
6
6
  update: (teams: IBaseTeam[]) => void;
7
+ /**
8
+ * Merge what just changed into the team of the list — the way a settings
9
+ * save keeps every surface reading this store current (avatar, banner,
10
+ * name…) without refetching the list.
11
+ */
12
+ updateTeam: (team: Partial<IBaseTeam> & Pick<IBaseTeam, 'id'>) => void;
7
13
  };
8
14
  export declare const teamStore: import("zustand").StoreApi<TeamState>;
9
15
  export declare function useTeamStore(): TeamState;
@@ -10,6 +10,9 @@ export const teamStore = createStore((set, get) => ({
10
10
  update: (teams) => set((state) => ({
11
11
  teams,
12
12
  })),
13
+ updateTeam: team => set((state) => ({
14
+ teams: state.teams.map(known => known.id === team.id ? { ...known, ...team } : known),
15
+ })),
13
16
  }));
14
17
  export function useTeamStore(selector) {
15
18
  return useStore(teamStore, selector);
@@ -2,6 +2,7 @@ export * from './CoreState';
2
2
  export * from './DatasourceState';
3
3
  export * from './IAMState';
4
4
  export * from './LayoutState';
5
+ export * from './ProfileState';
5
6
  export * from './OrganizationState';
6
7
  export * from './SurveysState';
7
8
  export * from './TeamState';
@@ -6,6 +6,7 @@ export * from './CoreState.js';
6
6
  export * from './DatasourceState.js';
7
7
  export * from './IAMState.js';
8
8
  export * from './LayoutState.js';
9
+ export * from './ProfileState.js';
9
10
  export * from './OrganizationState.js';
10
11
  export * from './SurveysState.js';
11
12
  export * from './TeamState.js';
@@ -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
  }
@@ -16,7 +16,7 @@ export interface SignInSimpleProps {
16
16
  onSignIn?: (token: string, handle: string) => void;
17
17
  /**
18
18
  * Called when the user authenticates with an API key.
19
- * If not provided the "Sign In with an API Key" button is hidden.
19
+ * If not provided the "Sign in with an API Key" button is hidden.
20
20
  */
21
21
  onApiKeySignIn?: (apiKey: string) => void;
22
22
  /**
@@ -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,43 @@ 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 ?? (asDoc && title === 'Datalayer OTEL' ? 'Datalayer Sign In' : title);
107
+ const headingIcon = icon ?? (asDoc ? _jsx(SignInIcon, { size: 24 }) : leadingIcon);
74
108
  const loginUrl = useMemo(() => {
75
109
  if (loginUrlProp)
76
110
  return loginUrlProp;
@@ -280,104 +314,104 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
280
314
  }
281
315
  }, [mfaUserUid, submit, submitMfa]);
282
316
  const socialDisabled = socialLoading || loading || asDoc;
283
- return (_jsx(Box, { sx: {
317
+ return (_jsxs(Box, { className: "signin-input-theme-scope", sx: {
284
318
  display: 'flex',
285
- alignItems: hideHero ? 'flex-start' : 'center',
319
+ alignItems: hideHero || compactDocMode ? 'flex-start' : 'center',
286
320
  justifyContent: 'center',
287
- minHeight: hideHero ? 'auto' : '100vh',
321
+ minHeight: hideHero || compactDocMode ? 'auto' : '100vh',
288
322
  bg: hideHero ? 'transparent' : 'canvas.default',
289
323
  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%)',
324
+ py: hideHero ? 0 : compactDocMode ? 2 : 4,
325
+ }, children: [_jsx(SignInInputGlobalStyle, {}), _jsxs(Box, { sx: {
326
+ width: '100%',
327
+ maxWidth: 440,
328
+ p: 0,
329
+ }, children: [!hideHero && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
330
+ display: 'flex',
331
+ alignItems: 'center',
332
+ gap: 2,
333
+ mb: compactDocMode ? 2 : 3,
334
+ justifyContent: 'center',
335
+ }, children: [headingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: headingText })] }), _jsx(Text, { as: "p", sx: {
336
+ fontSize: 1,
337
+ color: 'fg.muted',
338
+ mb: compactDocMode ? 2 : 3,
339
+ textAlign: 'center',
340
+ }, 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'
341
+ ? () => _jsx(Spinner, { size: "small" })
342
+ : GithubMarkIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.github), sx: {
343
+ mt: socialButtonMarginTop,
344
+ maxWidth: actionButtonMaxWidth,
345
+ mx: 'auto',
346
+ }, children: "Sign in with GitHub" })), google && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'google'
347
+ ? () => _jsx(Spinner, { size: "small" })
348
+ : GoogleIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.google), sx: {
349
+ mt: socialButtonMarginTop,
350
+ maxWidth: actionButtonMaxWidth,
351
+ mx: 'auto',
352
+ }, children: "Sign in with Google" })), linkedin && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'linkedin'
353
+ ? () => _jsx(Spinner, { size: "small" })
354
+ : LinkedInGreyIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.linkedin), sx: {
355
+ mt: socialButtonMarginTop,
356
+ maxWidth: actionButtonMaxWidth,
357
+ mx: 'auto',
358
+ }, 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: {
359
+ display: 'flex',
360
+ alignItems: 'center',
361
+ justifyContent: 'space-between',
362
+ mt: 3,
363
+ }, children: [_jsx(Button, { variant: "primary", disabled: loading || asDoc || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign In' }), showForgotPassword && (_jsx(Link, { as: "button", sx: {
364
+ fontSize: 1,
365
+ cursor: asDoc ? 'not-allowed' : 'pointer',
366
+ background: 'none',
367
+ border: 'none',
368
+ opacity: asDoc ? 0.6 : 1,
369
+ }, onClick: () => {
370
+ if (asDoc || loading) {
371
+ return;
372
+ }
373
+ handleForgotPassword();
374
+ }, 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: {
375
+ mt: 4,
376
+ pt: 4,
377
+ textAlign: 'center',
378
+ borderTop: '1px solid',
379
+ borderColor: 'border.muted',
380
+ }, 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: {
381
+ display: 'flex',
382
+ alignItems: 'center',
383
+ gap: 2,
384
+ my: 3,
385
+ }, 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 => {
386
+ if (event.key === 'Escape') {
387
+ closeApiKeyDialog();
388
+ }
389
+ }, onClick: closeApiKeyDialog, sx: {
390
+ position: 'fixed',
391
+ inset: 0,
392
+ zIndex: 200,
369
393
  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" })] })] }) }))] }))] }) }));
394
+ bg: 'canvas.backdrop',
395
+ display: 'flex',
396
+ alignItems: 'center',
397
+ justifyContent: 'center',
398
+ }, children: _jsxs(Box, { as: "form", onClick: event => event.stopPropagation(), onSubmit: event => {
399
+ event.preventDefault();
400
+ handleApiKeyAuthenticate();
401
+ }, sx: {
402
+ width: 'min(560px, 100%)',
403
+ p: 3,
404
+ borderRadius: 2,
405
+ border: '1px solid',
406
+ borderColor: 'border.default',
407
+ bg: 'canvas.overlay',
408
+ color: 'fg.default',
409
+ boxShadow: 'shadow.large',
410
+ }, 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: {
411
+ mt: 3,
412
+ display: 'flex',
413
+ justifyContent: 'flex-end',
414
+ gap: 2,
415
+ }, children: [_jsx(Button, { type: "button", onClick: closeApiKeyDialog, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: !apiKeyValue.trim(), children: "Authenticate" })] })] }) }))] }))] })] }));
382
416
  };
383
417
  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.56",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -90,7 +90,7 @@
90
90
  "watch:lib:src": "tsc -b -w",
91
91
  "type-check:watch": "tsc -b -w --noEmit",
92
92
  "examples": "run-p jupyter:start examples:vite",
93
- "examples:vite": "VITE_DATALAYER_URL=http://localhost:8888 vite --config vite.examples.config.ts",
93
+ "examples:vite": "VITE_DATALAYER_IAM_URL=http://localhost:8888 vite --config vite.examples.config.ts",
94
94
  "examples:nextjs": "npm run dev --workspace=nextjj-example",
95
95
  "jupyter:start": "./dev/sh/start-jupyter-server.sh",
96
96
  "prepare": "husky || true",
@@ -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 {};