diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index 9735e65977..ee51e50a1a 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -168,8 +168,13 @@ export type ProfileInfo = { picture?: string; }; -export type ObservableSession = { - session$(): Observable; +export enum SessionState { + SignedIn = 'SignedIn', + SignedOut = 'SignedOut', +} + +export type ObservableSessionStateApi = { + sessionState$(): Observable; }; /** * Provides authentication towards Google APIs and identities. @@ -180,7 +185,7 @@ export type ObservableSession = { * email and expiration information. Do not rely on any other fields, as they might not be present. */ export const googleAuthApiRef = createApiRef< - OAuthApi & OpenIdConnectApi & ProfileInfoApi & ObservableSession + OAuthApi & OpenIdConnectApi & ProfileInfoApi & ObservableSessionStateApi >({ id: 'core.auth.google', description: 'Provides authentication towards Google APIs and identities', @@ -192,7 +197,9 @@ export const googleAuthApiRef = createApiRef< * See https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ * for a full list of supported scopes. */ -export const githubAuthApiRef = createApiRef({ +export const githubAuthApiRef = createApiRef< + OAuthApi & ObservableSessionStateApi +>({ id: 'core.auth.github', description: 'Provides authentication towards Github APIs', }); diff --git a/packages/core-api/src/apis/implementations/auth/SessionStateTracker.ts b/packages/core-api/src/apis/implementations/auth/SessionStateTracker.ts new file mode 100644 index 0000000000..794953b07f --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/SessionStateTracker.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BehaviorSubject } from '../../../lib'; +import { SessionState } from '../..'; + +export class SessionStateTracker { + private signedIn: boolean = false; + observable = new BehaviorSubject(SessionState.SignedOut); + + setIsSignedId(isSignedIn: boolean) { + if (this.signedIn !== isSignedIn) { + this.signedIn = isSignedIn; + this.observable.next( + this.signedIn ? SessionState.SignedIn : SessionState.SignedOut, + ); + } + } +} diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts index 7322ddeabf..23a642196b 100644 --- a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts @@ -20,13 +20,14 @@ import { GithubSession } from './types'; import { OAuthApi, AccessTokenOptions, - ObservableSession, + ObservableSessionStateApi, + SessionState, } from '../../../definitions/auth'; import { OAuthRequestApi, AuthProvider } from '../../../definitions'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { StaticAuthSessionManager } from '../../../../lib/AuthSessionManager'; -import { BehaviorSubject } from '../../../../lib'; import { Observable } from '../../../../types'; +import { SessionStateTracker } from '../SessionStateTracker'; type CreateOptions = { // TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GithubAuth @@ -52,7 +53,7 @@ const DEFAULT_PROVIDER = { icon: GithubIcon, }; -class GithubAuth implements OAuthApi, ObservableSession { +class GithubAuth implements OAuthApi, ObservableSessionStateApi { static create({ apiOrigin, basePath, @@ -84,12 +85,10 @@ class GithubAuth implements OAuthApi, ObservableSession { return new GithubAuth(sessionManager); } - private readonly subject = new BehaviorSubject( - undefined, - ); + private readonly sessionStateTracker = new SessionStateTracker(); - session$(): Observable { - return this.subject; + sessionState$(): Observable { + return this.sessionStateTracker.observable; } constructor(private readonly sessionManager: SessionManager) {} @@ -100,7 +99,7 @@ class GithubAuth implements OAuthApi, ObservableSession { ...options, scopes: normalizedScopes, }); - this.subject.next(!!session); + this.sessionStateTracker.setIsSignedId(!!session); if (session) { return session.accessToken; } @@ -109,7 +108,7 @@ class GithubAuth implements OAuthApi, ObservableSession { async logout() { await this.sessionManager.removeSession(); - this.subject.next(false); + this.sessionStateTracker.setIsSignedId(false); } static normalizeScope(scope?: string): Set { diff --git a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts index 82048ee8ae..e4d2d7c81d 100644 --- a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts @@ -25,13 +25,14 @@ import { ProfileInfoApi, ProfileInfoOptions, ProfileInfo, - ObservableSession, + ObservableSessionStateApi, + SessionState, } from '../../../definitions/auth'; import { OAuthRequestApi, AuthProvider } from '../../../definitions'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; -import { BehaviorSubject } from '../../../../lib'; import { Observable } from '../../../../types'; +import { SessionStateTracker } from '../SessionStateTracker'; type CreateOptions = { // TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GoogleAuth @@ -61,7 +62,11 @@ const DEFAULT_PROVIDER = { const SCOPE_PREFIX = 'https://www.googleapis.com/auth/'; class GoogleAuth - implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, ObservableSession { + implements + OAuthApi, + OpenIdConnectApi, + ProfileInfoApi, + ObservableSessionStateApi { static create({ apiOrigin, basePath, @@ -103,12 +108,10 @@ class GoogleAuth return new GoogleAuth(sessionManager); } - private readonly subject = new BehaviorSubject( - undefined, - ); + private readonly sessionStateTracker = new SessionStateTracker(); - session$(): Observable { - return this.subject; + sessionState$(): Observable { + return this.sessionStateTracker.observable; } constructor(private readonly sessionManager: SessionManager) {} @@ -122,7 +125,7 @@ class GoogleAuth ...options, scopes: normalizedScopes, }); - this.subject.next(!!session); + this.sessionStateTracker.setIsSignedId(!!session); if (session) { return session.accessToken; } @@ -131,7 +134,7 @@ class GoogleAuth async getIdToken(options: IdTokenOptions = {}) { const session = await this.sessionManager.getSession(options); - this.subject.next(!!session); + this.sessionStateTracker.setIsSignedId(!!session); if (session) { return session.idToken; } @@ -140,7 +143,7 @@ class GoogleAuth async logout() { await this.sessionManager.removeSession(); - this.subject.next(false); + this.sessionStateTracker.setIsSignedId(false); } async getProfile(options: ProfileInfoOptions = {}) { diff --git a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts index ee7cbab2ae..793c6f1708 100644 --- a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts +++ b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts @@ -131,15 +131,6 @@ describe('RefreshingAuthSessionManager', () => { }); it('should remove session and reload', async () => { - // This is a workaround that is used by Facebook and the Jest core team - // It is a limitation with the newest versions of JSDOM, and newer browser standards - // where window.location and all of its properties are read-only. So we re-construct it! - // See https://github.com/facebook/jest/issues/890#issuecomment-209698782 - const location = { ...window.location }; - delete window.location; - window.location = location; - jest.spyOn(window.location, 'reload').mockImplementation(); - const removeSession = jest.fn(); const manager = new RefreshingAuthSessionManager({ connector: { removeSession }, @@ -147,7 +138,7 @@ describe('RefreshingAuthSessionManager', () => { } as any); await manager.removeSession(); - expect(window.location.reload).toHaveBeenCalled(); expect(removeSession).toHaveBeenCalled(); + expect(await manager.getSession({ optional: true })).toBe(undefined); }); }); diff --git a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts index 7d47fa91df..6280750875 100644 --- a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts +++ b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts @@ -84,11 +84,6 @@ describe('StaticAuthSessionManager', () => { }); it('should remove session and reload', async () => { - const location = { ...window.location }; - delete window.location; - window.location = location; - jest.spyOn(window.location, 'reload').mockImplementation(); - const removeSession = jest.fn(); const manager = new StaticAuthSessionManager({ connector: { removeSession }, @@ -96,7 +91,7 @@ describe('StaticAuthSessionManager', () => { } as any); await manager.removeSession(); - expect(window.location.reload).toHaveBeenCalled(); expect(removeSession).toHaveBeenCalled(); + expect(await manager.getSession({ optional: true })).toBe(undefined); }); }); diff --git a/packages/core/src/layout/Sidebar/Items.tsx b/packages/core/src/layout/Sidebar/Items.tsx index 0a875c9035..4c7d4487da 100644 --- a/packages/core/src/layout/Sidebar/Items.tsx +++ b/packages/core/src/layout/Sidebar/Items.tsx @@ -58,8 +58,11 @@ const useStyles = makeStyles(theme => { // XXX (@koroeskohr): I can't seem to achieve the desired font-weight from the designs fontWeight: 'bold', whiteSpace: 'nowrap', - lineHeight: 1.0, + lineHeight: 'auto', flex: '3 1 auto', + width: '110px', + overflow: 'hidden', + 'text-overflow': 'ellipsis', }, iconContainer: { boxSizing: 'border-box', diff --git a/packages/core/src/layout/Sidebar/Settings/OAuthProviderSettings.tsx b/packages/core/src/layout/Sidebar/Settings/OAuthProviderSettings.tsx new file mode 100644 index 0000000000..bbc1ca8e18 --- /dev/null +++ b/packages/core/src/layout/Sidebar/Settings/OAuthProviderSettings.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ApiRef, + OAuthApi, + ObservableSessionStateApi, + useApi, + Subscription, + IconComponent, + SessionState, +} from '@backstage/core-api'; +import React, { FC, useState, useEffect } from 'react'; +import { ProviderSettingsItem } from './ProviderSettingsItem'; + +type OAuthProviderSidebarProps = { + title: string; + icon: IconComponent; + apiRef: ApiRef; +}; + +export const OAuthProviderSettings: FC = ({ + title, + icon, + apiRef, +}) => { + const api = useApi(apiRef); + const [signedIn, setSignedIn] = useState(false); + + useEffect(() => { + const checkSession = async () => { + const session = await api.getAccessToken('', { optional: true }); + setSignedIn(!!session); + }; + let subscription: Subscription; + const observeSession = () => { + subscription = api + .sessionState$() + .subscribe((sessionState: SessionState) => { + setSignedIn(sessionState === SessionState.SignedIn); + }); + }; + + checkSession(); + observeSession(); + return () => { + subscription.unsubscribe(); + }; + }, [api]); + + return ( + api.getAccessToken()} + /> + ); +}; diff --git a/packages/core/src/layout/Sidebar/Settings/OIDCProviderSettings.tsx b/packages/core/src/layout/Sidebar/Settings/OIDCProviderSettings.tsx new file mode 100644 index 0000000000..ea1f63b76c --- /dev/null +++ b/packages/core/src/layout/Sidebar/Settings/OIDCProviderSettings.tsx @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ApiRef, + OpenIdConnectApi, + ObservableSessionStateApi, + useApi, + Subscription, + IconComponent, + SessionState, +} from '@backstage/core-api'; +import React, { FC, useState, useEffect } from 'react'; +import { ProviderSettingsItem } from './ProviderSettingsItem'; + +export type OIDCProviderSidebarProps = { + title: string; + icon: IconComponent; + apiRef: ApiRef; +}; + +export const OIDCProviderSettings: FC = ({ + title, + icon, + apiRef, +}) => { + const api = useApi(apiRef); + const [signedIn, setSignedIn] = useState(false); + + useEffect(() => { + const checkSession = async () => { + const session = await api.getIdToken({ optional: true }); + setSignedIn(!!session); + }; + + let subscription: Subscription; + const observeSession = () => { + subscription = api + .sessionState$() + .subscribe((sessionState: SessionState) => { + setSignedIn(sessionState === SessionState.SignedIn); + }); + }; + + checkSession(); + observeSession(); + return () => { + subscription.unsubscribe(); + }; + }, [api]); + + return ( + api.getIdToken()} + /> + ); +}; diff --git a/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx b/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx new file mode 100644 index 0000000000..8fbc945cc2 --- /dev/null +++ b/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { FC } from 'react'; +import { OAuthApi, OpenIdConnectApi, IconComponent } from '@backstage/core-api'; +import { SidebarItem } from '../Items'; +import { IconButton, Tooltip } from '@material-ui/core'; +import StarBorder from '@material-ui/icons/StarBorder'; +import PowerButton from '@material-ui/icons/PowerSettingsNew'; + +export const ProviderSettingsItem: FC<{ + title: string; + icon: IconComponent; + signedIn: boolean; + api: OAuthApi | OpenIdConnectApi; + signInHandler: Function; +}> = ({ title, icon, signedIn, api, signInHandler }) => { + return ( + + (signedIn ? api.logout() : signInHandler())}> + + + + + + ); +}; diff --git a/packages/core/src/layout/Sidebar/Settings/UserProfile.tsx b/packages/core/src/layout/Sidebar/Settings/UserProfile.tsx new file mode 100644 index 0000000000..ebf1d0877d --- /dev/null +++ b/packages/core/src/layout/Sidebar/Settings/UserProfile.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { FC, useState, useRef, useEffect } from 'react'; +import { makeStyles, Avatar, Divider } from '@material-ui/core'; +import { + ProfileInfo, + useApi, + googleAuthApiRef, + Subscription, + SessionState, +} from '@backstage/core-api'; +import { SidebarItem } from '../Items'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import AccountCircleIcon from '@material-ui/icons/AccountCircle'; + +const useStyles = makeStyles({ + avatar: { + width: 24, + height: 24, + }, +}); + +export const UserProfile: FC<{ open: boolean; setOpen: Function }> = ({ + open, + setOpen, +}) => { + const [profile, setProfile] = useState(); + const ref = useRef(); // for scrolling down when collapse item opens + const googleAuth = useApi(googleAuthApiRef); + const classes = useStyles(); + + const handleClick = () => { + setOpen(!open); + setTimeout(() => ref.current?.scrollIntoView({ behavior: 'smooth' }), 300); + }; + + useEffect(() => { + const fetchProfile = async () => { + await googleAuth + .getProfile({ optional: true }) + .then((userProfile?: ProfileInfo) => { + setProfile(userProfile); + }); + }; + + let subscription: Subscription; + const observeSession = () => { + subscription = googleAuth + .sessionState$() + .subscribe(async (sessionState: SessionState) => { + if (sessionState === SessionState.SignedIn) { + await fetchProfile(); + } else { + setProfile(undefined); + } + }); + }; + + fetchProfile(); + observeSession(); + return () => { + subscription.unsubscribe(); + }; + }, [googleAuth]); + + // Handle main auth info that is shown on the collapsible SidebarItem + let avatar; + let displayName = 'Guest'; + if (profile) { + const email = profile.email; + const name = profile.name; + const imageUrl = profile.picture; + const emailTrimmed = email.split('@')[0]; + const displayEmail = + emailTrimmed.charAt(0).toUpperCase() + emailTrimmed.slice(1); + displayName = name ?? displayEmail; + avatar = imageUrl + ? () => ( + + ) + : () => ; + } + + return ( + <> + + + {open ? : } + + + ); +}; diff --git a/packages/core/src/layout/Sidebar/Settings/index.ts b/packages/core/src/layout/Sidebar/Settings/index.ts new file mode 100644 index 0000000000..6557ace53a --- /dev/null +++ b/packages/core/src/layout/Sidebar/Settings/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ProviderSettingsItem } from './ProviderSettingsItem'; +export { OAuthProviderSettings } from './OAuthProviderSettings'; +export { OIDCProviderSettings } from './OIDCProviderSettings'; +export { UserProfile } from './UserProfile'; diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx index e63235434a..8ca2580df9 100644 --- a/packages/core/src/layout/Sidebar/UserSettings.tsx +++ b/packages/core/src/layout/Sidebar/UserSettings.tsx @@ -14,217 +14,16 @@ * limitations under the License. */ -import React, { useState, useContext, useEffect, useRef, FC } from 'react'; +import React, { useContext, useEffect } from 'react'; import Collapse from '@material-ui/core/Collapse'; -import ExpandLess from '@material-ui/icons/ExpandLess'; -import ExpandMore from '@material-ui/icons/ExpandMore'; -import StarBorder from '@material-ui/icons/StarBorder'; import Star from '@material-ui/icons/Star'; import { SidebarContext } from './config'; -import { SidebarItem } from './Items'; -import AccountCircleIcon from '@material-ui/icons/AccountCircle'; -import Divider from '@material-ui/core/Divider'; +import { googleAuthApiRef, githubAuthApiRef } from '@backstage/core-api'; import { - useApi, - googleAuthApiRef, - githubAuthApiRef, - ProfileInfo, - OAuthApi, - OpenIdConnectApi, - ProfileInfoApi, - ApiRef, - ObservableSession, -} from '@backstage/core-api'; -import { Avatar, IconButton, makeStyles, Tooltip } from '@material-ui/core'; -import PowerButton from '@material-ui/icons/PowerSettingsNew'; - -type Provider = { - title: string; - icon: any; -}; - -type OAuthProviderSidebarProps = { - title: string; - icon: any; - apiRef: ApiRef; -}; - -type OIDCProviderSidebarProps = { - title: string; - icon: any; - apiRef: ApiRef; -}; - -const OAuthProviderSidebarComponent: FC = ({ - title, - icon, - apiRef, -}) => { - const api = useApi(apiRef); - const [signedIn, setSignedIn] = useState(false); - - useEffect(() => { - const checkSession = async () => { - const session = await api.getAccessToken('', { optional: true }); - setSignedIn(!!session); - }; - - const observeSession = () => { - api.session$().subscribe((signedInState: boolean) => { - if (signedIn !== signedInState) { - setSignedIn(signedInState); - } - }); - }; - - checkSession(); - observeSession(); - }, []); - - return ( - - ); -}; - -const OIDCProviderSidebarComponent: FC = ({ - title, - icon, - apiRef, -}) => { - const api = useApi(apiRef); - const [signedIn, setSignedIn] = useState(false); - - useEffect(() => { - const checkSession = async () => { - const session = await api.getIdToken({ optional: true }); - setSignedIn(!!session); - }; - - const observeSession = () => { - api.session$().subscribe((signedInState: boolean) => { - if (signedIn !== signedInState) { - setSignedIn(signedInState); - } - }); - }; - - checkSession(); - observeSession(); - }, []); - - return ( - - ); -}; - -const ProviderSidebarItemComponent: FC<{ - title: string; - icon: any; - signedIn: boolean; - api: OAuthApi | OpenIdConnectApi; - signInHandler: Function; -}> = ({ title, icon, signedIn, api, signInHandler }) => { - return ( - - (signedIn ? api.logout() : signInHandler())}> - - - - - - ); -}; - -const useStyles = makeStyles({ - avatar: { - width: 24, - height: 24, - }, -}); - -export const SidebarUserProfile: FC<{ setOpen: Function }> = ({ setOpen }) => { - const [profile, setProfile] = useState(); - const ref = useRef(); // for scrolling down when collapse item opens - const googleAuth = useApi(googleAuthApiRef); - const classes = useStyles(); - - const handleClick = () => { - setOpen(!open); - setTimeout(() => ref.current?.scrollIntoView({ behavior: 'smooth' }), 300); - }; - - useEffect(() => { - const fetchProfile = async () => { - await googleAuth - .getProfile({ optional: true }) - .then((userProfile?: ProfileInfo) => { - setProfile(userProfile); - }); - }; - - const observeSession = () => { - googleAuth.session$().subscribe(() => { - fetchProfile(); - }); - }; - - fetchProfile(); - observeSession(); - }, [open]); - - // Handle main auth info that is shown on the collapsible SidebarItem - let avatar; - let displayName = 'Guest'; - if (profile) { - const email = profile.email; - const name = profile.name; - const imageUrl = profile.picture; - const emailTrimmed = email.split('@')[0]; - const displayEmail = - emailTrimmed.charAt(0).toUpperCase() + emailTrimmed.slice(1); - displayName = name ?? displayEmail; - avatar = imageUrl - ? () => ( - - ) - : () => ; - } - - return ( - <> - - - {open ? : } - - - ); -}; + OAuthProviderSettings, + OIDCProviderSettings, + UserProfile as SidebarUserProfile, +} from './Settings'; export function SidebarUserSettings() { const { isOpen: sidebarOpen } = useContext(SidebarContext); @@ -237,14 +36,14 @@ export function SidebarUserSettings() { return ( <> - - - + + -