Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 69 additions & 33 deletions apps/sensenet/src/components/app-providers.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { PathHelper } from '@sensenet/client-utils'
import { InjectorContext, LoggerContextProvider } from '@sensenet/hooks-react'
import React, { ReactNode, Suspense, useCallback, useEffect, useState } from 'react'
import { BrowserRouter } from 'react-router-dom'
Expand All @@ -8,6 +7,7 @@ import {
LocalizationProvider,
PersonalSettingsContextProvider,
RepositoryProvider,
RepositorySwitchContext,
ResponsiveContextProvider,
ThemeProvider,
} from '../context'
Expand All @@ -22,6 +22,12 @@ import {
NavigationCommandProvider,
SearchCommandProvider,
} from '../services'
import {
clearActiveRepositorySelection,
hasSnAuthRepositoryTokens,
normalizeRepositoryUrl,
startSnAuthRepositoryLogin,
} from '../services/repository-session'
import { DialogProvider } from './dialogs/dialog-provider'

import { GridLoadingProvider } from './grid/Providers/GridLoadingProvider'
Expand All @@ -34,28 +40,56 @@ export type AppProvidersProps = {
}

export default function AppProviders({ children }: AppProvidersProps) {
const initAuthType: AuthServerType = (window.localStorage.getItem('authType') as AuthServerType) ?? 'IdentityServer'
const initAuthType: AuthServerType =
(window.localStorage.getItem('authType') as AuthServerType) ?? defaultAuthConfig.authType
const [authType, setAuthType] = useState<'IdentityServer' | 'SNAuth'>(initAuthType)
const [url, setUrl] = useState<string>('')

const selectRepository = useCallback((providedUrl: string) => {
const normalizedUrl = normalizeRepositoryUrl(providedUrl)

clearActiveRepositorySelection()
startSnAuthRepositoryLogin(normalizedUrl)
setUrl(normalizedUrl)
}, [])

const changeAuthType = useCallback((providedUrl: string) => {
setUrl(PathHelper.ensureDefaultSchema(providedUrl))
const normalizedUrl = normalizeRepositoryUrl(providedUrl)

setUrl(normalizedUrl)
setAuthType((prev) => {
const newAuthType = prev === 'IdentityServer' ? 'SNAuth' : 'IdentityServer'
if (newAuthType === 'SNAuth') {
startSnAuthRepositoryLogin(normalizedUrl)
}
window.localStorage.setItem('authType', newAuthType)
return newAuthType
})
}, [])

const switchRepository = useCallback((providedUrl: string) => {
const normalizedUrl = normalizeRepositoryUrl(providedUrl)

if (!hasSnAuthRepositoryTokens(normalizedUrl)) {
startSnAuthRepositoryLogin(normalizedUrl)
}

window.localStorage.setItem('authType', 'SNAuth')
setAuthType('SNAuth')
setUrl(normalizedUrl)
}, [])

useEffect(() => {
const IsAuthKey = localStorage.getItem(authConfigKeyIS)
const SnAuthKey = localStorage.getItem(authConfigKeySN)
if (IsAuthKey || SnAuthKey) return
const repoUrl = new URL(window.location.href).searchParams.get('repoUrl')
if (repoUrl) {
changeAuthType(repoUrl)
selectRepository(repoUrl)
return
}
}, [changeAuthType])

const IsAuthKey = localStorage.getItem(authConfigKeyIS)
const SnAuthKey = localStorage.getItem(authConfigKeySN)
if (IsAuthKey || SnAuthKey) return
}, [selectRepository])

snInjector
.getInstance(CommandProviderManager)
Expand All @@ -76,31 +110,33 @@ export default function AppProviders({ children }: AppProvidersProps) {
<GridLoadingProvider>
<TreeLoadingProvider>
<ThemeProvider>
{authType === 'IdentityServer' ? (
<RepositoryProvider url={url} changeAuthType={changeAuthType}>
<ShareProvider>
<ISAuthProvider>
<ResponsiveContextProvider>
<ExpandedItemsProvider>
<DialogProvider>{children}</DialogProvider>
</ExpandedItemsProvider>
</ResponsiveContextProvider>
</ISAuthProvider>
</ShareProvider>
</RepositoryProvider>
) : (
<SnAuthRepositoryProvider url={url} changeAuthType={changeAuthType}>
<ShareProvider>
<SNAuthProvider>
<ResponsiveContextProvider>
<ExpandedItemsProvider>
<DialogProvider>{children}</DialogProvider>
</ExpandedItemsProvider>
</ResponsiveContextProvider>
</SNAuthProvider>
</ShareProvider>
</SnAuthRepositoryProvider>
)}
<RepositorySwitchContext.Provider value={{ authType, switchRepository }}>
{authType === 'IdentityServer' ? (
<RepositoryProvider url={url} changeAuthType={changeAuthType}>
<ShareProvider>
<ISAuthProvider>
<ResponsiveContextProvider>
<ExpandedItemsProvider>
<DialogProvider>{children}</DialogProvider>
</ExpandedItemsProvider>
</ResponsiveContextProvider>
</ISAuthProvider>
</ShareProvider>
</RepositoryProvider>
) : (
<SnAuthRepositoryProvider url={url} changeAuthType={changeAuthType}>
<ShareProvider>
<SNAuthProvider>
<ResponsiveContextProvider>
<ExpandedItemsProvider>
<DialogProvider>{children}</DialogProvider>
</ExpandedItemsProvider>
</ResponsiveContextProvider>
</SNAuthProvider>
</ShareProvider>
</SnAuthRepositoryProvider>
)}
</RepositorySwitchContext.Provider>
</ThemeProvider>
</TreeLoadingProvider>
</GridLoadingProvider>
Expand Down
23 changes: 22 additions & 1 deletion apps/sensenet/src/components/app.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
import { CssBaseline, StylesProvider } from '@material-ui/core'
import React from 'react'
import { clearActiveRepositorySelection } from '../services/repository-session'
import AppProviders from './app-providers'
import { Dialogs } from './dialogs'
import { ErrorBoundary } from './error-boundary'
import { DesktopLayout } from './layout/DesktopLayout'
import { MainRouter } from './MainRouter'
import { NotificationComponent } from './NotificationComponent'

const AppErrorFallback = () => {
const switchRepository = () => {
clearActiveRepositorySelection()
window.location.assign('/')
}

return (
<div style={{ boxSizing: 'border-box', minHeight: '100vh', padding: 32 }}>
<h1>Something went wrong</h1>
<p>The current repository could not be loaded. You can reload the page or choose another repository.</p>
<button type="button" onClick={() => window.location.reload()} style={{ marginRight: 16 }}>
Reload
</button>
<button type="button" onClick={switchRepository}>
Switch repository
</button>
</div>
)
}

export function App() {
return (
<ErrorBoundary>
<ErrorBoundary FallbackComponent={AppErrorFallback}>
<AppProviders>
<CssBaseline />
<StylesProvider injectFirst>
Expand Down
12 changes: 10 additions & 2 deletions apps/sensenet/src/components/dialogs/logout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Button, DialogActions, DialogContent, DialogContentText } from '@material-ui/core'
import { useRepository } from '@sensenet/hooks-react'
import React from 'react'
import { authConfigKey } from '../../context'
import { useAuth } from '../../context/auth-provider'
import { useGlobalStyles } from '../../globalStyles'
import { useLocalization } from '../../hooks'
import { clearActiveRepositorySelection } from '../../services/repository-session'
import { Icon } from '../Icon'
import { DialogTitle, useDialog } from '.'

Expand Down Expand Up @@ -38,6 +38,14 @@ export function LogoutDialog() {
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
aria-label={localization.switchRepositoryButtonTitle}
onClick={() => {
clearActiveRepositorySelection()
window.location.assign('/')
}}>
{localization.switchRepositoryButtonTitle}
</Button>
<Button
aria-label={localization.logoutCancel}
className={globalClasses.cancelButton}
Expand All @@ -49,7 +57,7 @@ export function LogoutDialog() {
color="primary"
variant="contained"
onClick={() => {
window.localStorage.removeItem(authConfigKey)
clearActiveRepositorySelection()
logout()
}}
autoFocus={true}>
Expand Down
2 changes: 2 additions & 0 deletions apps/sensenet/src/components/drawer/PermanentDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useDrawerItems, useLocalization } from '../../hooks'
import { AddButton } from '../AddButton'
import { SearchButton } from '../search-button'
import { PermanentDrawerItem } from './PermanentDrawerItem'
import { RepositorySelector } from './repository-selector'

const useStyles = makeStyles((theme: Theme) => {
return createStyles({
Expand Down Expand Up @@ -109,6 +110,7 @@ export const PermanentDrawer = () => {
</ListItemIcon>
</ListItem>
) : null}
{opened ? <RepositorySelector /> : null}
{matchPath(location.pathname, PATHS.savedQueries.appPath) ? <SearchButton isOpened={opened} /> : null}{' '}
{matchPath(location.pathname, [
PATHS.content.appPath,
Expand Down
2 changes: 2 additions & 0 deletions apps/sensenet/src/components/drawer/TemporaryDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ResponsiveContext, ResponsivePersonalSettings } from '../../context'
import { useDrawerItems, useLocalization, useTheme } from '../../hooks'
import { LogoutButton } from '../LogoutButton'
import { UserAvatar } from '../UserAvatar'
import { RepositorySelector } from './repository-selector'

type TemporaryDrawerProps = {
isOpened: boolean
Expand Down Expand Up @@ -60,6 +61,7 @@ export const TemporaryDrawer = (props: TemporaryDrawerProps) => {
transition: 'width 100ms ease-in-out',
}}>
<div style={{ paddingTop: '1em' }}>
<RepositorySelector />
{items.map((item, index) => {
const isActive = matchPath(location.pathname, item.url)
return isActive ? (
Expand Down
79 changes: 79 additions & 0 deletions apps/sensenet/src/components/drawer/repository-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { FormControl, InputLabel, makeStyles, MenuItem, Select, Theme } from '@material-ui/core'
import { useRepository } from '@sensenet/hooks-react'
import React, { useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom'
import { PATHS } from '../../application-paths'
import { useRepositorySwitch } from '../../context'
import { useLocalization } from '../../hooks'
import {
getAuthenticatedSnAuthRepositorySessions,
normalizeRepositoryUrl,
snAuthRepositorySessionsChangedEvent,
} from '../../services/repository-session'

const useStyles = makeStyles((theme: Theme) => ({
root: {
padding: theme.spacing(1, 1, 0),
},
formControl: {
width: '100%',
},
}))

const getRepositoryHost = (repoUrl: string) => {
try {
return new URL(repoUrl).host
} catch {
return repoUrl
}
}

export const RepositorySelector = () => {
const classes = useStyles()
const history = useHistory()
const localization = useLocalization().repositorySelector
const repository = useRepository()
const { authType, switchRepository } = useRepositorySwitch()
const currentRepositoryUrl = normalizeRepositoryUrl(repository.configuration.repositoryUrl)
const [repositorySessions, setRepositorySessions] = useState(getAuthenticatedSnAuthRepositorySessions)

useEffect(() => {
const refreshRepositorySessions = () => setRepositorySessions(getAuthenticatedSnAuthRepositorySessions())

window.addEventListener(snAuthRepositorySessionsChangedEvent, refreshRepositorySessions)

return () => window.removeEventListener(snAuthRepositorySessionsChangedEvent, refreshRepositorySessions)
}, [])

if (authType !== 'SNAuth' || repositorySessions.length < 2) {
return null
}

return (
<div className={classes.root}>
<FormControl className={classes.formControl} variant="outlined" size="small">
<InputLabel id="repository-selector-label">{localization.activeRepository}</InputLabel>
<Select
labelId="repository-selector-label"
label={localization.activeRepository}
value={currentRepositoryUrl}
onChange={(ev) => {
const nextRepositoryUrl = ev.target.value as string

if (nextRepositoryUrl === currentRepositoryUrl) {
return
}

switchRepository(nextRepositoryUrl)
history.push(PATHS.landingPath.appPath)
}}>
{repositorySessions.map((repositorySession) => (
<MenuItem key={repositorySession.repoUrl} value={repositorySession.repoUrl}>
{getRepositoryHost(repositorySession.repoUrl)}
</MenuItem>
))}
</Select>
</FormControl>
</div>
)
}
37 changes: 36 additions & 1 deletion apps/sensenet/src/components/login/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
createStyles,
Grid,
InputLabel,
List,
ListItem,
ListItemText,
makeStyles,
TextField,
TextFieldProps,
Expand Down Expand Up @@ -50,9 +53,16 @@ const DEVDEMO_URL = `https://dev.demo.sensenet.com`
type LoginPageProps = {
handleSubmit: (url: string) => void
isLoginInProgress: boolean
repositoryOptions?: Array<{ repoUrl: string; lastUsed?: string }>
handleSelectRepository?: (url: string) => void
}

export default function LoginPage({ handleSubmit, isLoginInProgress }: LoginPageProps) {
export default function LoginPage({
handleSubmit,
isLoginInProgress,
repositoryOptions = [],
handleSelectRepository,
}: LoginPageProps) {
const classes = useStyles()
const globalClasses = useGlobalStyles()
const localization = useLocalization().login
Expand Down Expand Up @@ -143,6 +153,31 @@ export default function LoginPage({ handleSubmit, isLoginInProgress }: LoginPage
</Button>
</form>
</Grid>
{repositoryOptions.length > 0 && handleSelectRepository ? (
<Grid item style={{ marginTop: 32 }}>
<Typography align="center" variant="subtitle1" component="p" className={classes.loginSubtitle}>
{localization.recentRepositories}
</Typography>
<List dense={true}>
{repositoryOptions.map((repositoryOption) => (
<ListItem
button={true}
disabled={isLoginInProgress}
key={repositoryOption.repoUrl}
onClick={() => handleSelectRepository(repositoryOption.repoUrl)}>
<ListItemText
primary={repositoryOption.repoUrl}
secondary={
repositoryOption.lastUsed
? localization.lastUsedRepository(new Date(repositoryOption.lastUsed).toLocaleString())
: undefined
}
/>
</ListItem>
))}
</List>
</Grid>
) : null}
</Grid>
</Container>
</>
Expand Down
Loading
Loading