Skip to content
Snippets Groups Projects
Commit 6e39cc12 authored by Sivert Kronen Hatteberg's avatar Sivert Kronen Hatteberg
Browse files

Make the header and footer look like the wireframes

- Better loading of translations using suspense.
- Adds a "fetched" variable to the user objects. Enable us to show a
  spinner before the return of the userinfo call

Issue: GREG-72
parent 2004c0d0
No related branches found
No related tags found
1 merge request!97Greg 72 nicer footer and header
Showing
with 1156 additions and 128 deletions
...@@ -5,7 +5,7 @@ REACT_APP_SUPPORT_MAIL=test@example.org ...@@ -5,7 +5,7 @@ REACT_APP_SUPPORT_MAIL=test@example.org
REACT_APP_INST=uio REACT_APP_INST=uio
REACT_APP_SUPPORT_URL=https://example.org REACT_APP_SUPPORT_URL=https://example.org
REACT_APP_RESPONSIBLE_ORGANIZATION=N/A REACT_APP_RESPONSIBLE_ORGANIZATION='Seksjon for integrasjon og elektroniske identiteter (INT)'
REACT_APP_RESPONSIBLE_ORGANIZATION_LINK=https://example.org REACT_APP_RESPONSIBLE_ORGANIZATION_LINK='https://www.usit.uio.no/om/organisasjon/bnt/usitint/'
REACT_APP_THEME=default REACT_APP_THEME=default
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"project": "./tsconfig.json", "project": "./frontend/tsconfig.json",
"ecmaFeatures": { "ecmaFeatures": {
"jsx": true "jsx": true
}, },
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
}, },
"fnr": "Fødselsnummer", "fnr": "Fødselsnummer",
"header": { "header": {
"applicationTitle": "Gjesteregistrering", "applicationTitle": "Gjestetjenesten",
"applicationDescription": "Registreringstjeneste for gjester", "applicationDescription": "Registreringstjeneste for gjester",
"selectLanguage": "Velg språk" "selectLanguage": "Velg språk"
}, },
......
...@@ -3,5 +3,5 @@ ...@@ -3,5 +3,5 @@
"contactSectionHeader": "Trenger du hjelp?", "contactSectionHeader": "Trenger du hjelp?",
"contactHelp": "Kontakt", "contactHelp": "Kontakt",
"contactHelpMail": "Kontaktveileder", "contactHelpMail": "Kontaktveileder",
"responsibleOrganizationHeader": "Ansvarlig for tjenesten" "responsibleOrganizationHeader": "Ansvarlig for denne tjenesten"
} }
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
}, },
"fnr": "National identity number", "fnr": "National identity number",
"header": { "header": {
"applicationTitle": "Gjesteregistrering", "applicationTitle": "Gjestetenesta",
"applicationDescription": "Registreringsteneste for gjester", "applicationDescription": "Registreringsteneste for gjester",
"selectLanguage": "Velg språk" "selectLanguage": "Velg språk"
}, },
......
...@@ -3,5 +3,5 @@ ...@@ -3,5 +3,5 @@
"contactSectionHeader": "Treng du hjelp?", "contactSectionHeader": "Treng du hjelp?",
"contactHelp": "Kontakt", "contactHelp": "Kontakt",
"contactHelpMail": "Kontaktrettleiar", "contactHelpMail": "Kontaktrettleiar",
"responsibleOrganizationHeader": "Ansvarleg for tenesta" "responsibleOrganizationHeader": "Ansvarleg for denne tenesta"
} }
...@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react' ...@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { MenuItem, Select, SelectChangeEvent } from '@mui/material' import { MenuItem, Select, SelectChangeEvent } from '@mui/material'
function LanguageSelector() { function LanguageSelector({ noPadding }: { noPadding: boolean }) {
const { i18n, t } = useTranslation('common') const { i18n, t } = useTranslation('common')
const [selectedLanguage, setSelectedLanguage] = useState('en') const [selectedLanguage, setSelectedLanguage] = useState('en')
...@@ -22,22 +22,31 @@ function LanguageSelector() { ...@@ -22,22 +22,31 @@ function LanguageSelector() {
} }
} }
const sx: Record<string, any> = {
color: 'white',
'& .MuiSelect-icon': {
color: 'white',
},
}
if (noPadding) {
sx['& .MuiSelect-select'] = { paddingBottom: '0rem' }
}
return ( return (
<Select <Select
labelId="language-select" labelId="language-select"
label={t('header.selectLanguage')} label={t('header.selectLanguage')}
sx={{ defaultValue={selectedLanguage}
color: 'white', sx={sx}
'& .MuiSelect-icon': {
color: 'white',
},
}}
variant="standard" variant="standard"
value={selectedLanguage} value={selectedLanguage}
onChange={handleLanguageChange} onChange={handleLanguageChange}
> >
{Array.from(languageOptions.entries()).map((entry) => ( {Array.from(languageOptions.entries()).map((entry) => (
<MenuItem value={entry[0]}>{entry[1]}</MenuItem> <MenuItem key={entry[0]} value={entry[0]}>
{entry[1]}
</MenuItem>
))} ))}
</Select> </Select>
) )
......
import React from 'react' import React, { Suspense } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Spinner from 'components/animations/spinner' import Spinner from 'components/animations/spinner'
......
...@@ -2,6 +2,7 @@ import { createContext, useContext } from 'react' ...@@ -2,6 +2,7 @@ import { createContext, useContext } from 'react'
interface User { interface User {
auth: boolean auth: boolean
fetched: boolean
first_name: string first_name: string
last_name: string last_name: string
} }
...@@ -15,7 +16,7 @@ interface IUserContext { ...@@ -15,7 +16,7 @@ interface IUserContext {
function noop() {} function noop() {}
export const UserContext = createContext<IUserContext>({ export const UserContext = createContext<IUserContext>({
user: { auth: false, first_name: '', last_name: '' }, user: { auth: false, fetched: false, first_name: '', last_name: '' },
fetchUserInfo: noop, fetchUserInfo: noop,
clearUserInfo: noop, clearUserInfo: noop,
}) })
......
...@@ -21,9 +21,9 @@ i18n ...@@ -21,9 +21,9 @@ i18n
interpolation: { interpolation: {
escapeValue: false, escapeValue: false,
}, },
react: { // react: {
useSuspense: false, // useSuspense: false,
}, // },
detection: { detection: {
order: ['cookie'], order: ['cookie'],
caches: ['cookie'], caches: ['cookie'],
......
import React, { Suspense } from 'react' import React, { Suspense } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { ThemeProvider } from '@mui/material/styles' import { ThemeProvider } from '@mui/material/styles'
import { CircularProgress } from '@mui/material'
import AdapterDateFns from '@mui/lab/AdapterDateFns' import AdapterDateFns from '@mui/lab/AdapterDateFns'
import { LocalizationProvider } from '@mui/lab' import { LocalizationProvider } from '@mui/lab'
import { BrowserRouter as Router } from 'react-router-dom' import { BrowserRouter as Router } from 'react-router-dom'
...@@ -16,15 +17,15 @@ function appRoot() { ...@@ -16,15 +17,15 @@ function appRoot() {
return ( return (
<React.StrictMode> <React.StrictMode>
<Router> <Router>
<Suspense fallback={<Loading />}> <LocalizationProvider dateAdapter={AdapterDateFns}>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<ThemeProvider theme={getTheme()}> <ThemeProvider theme={getTheme()}>
<UserProvider> <UserProvider>
<App /> <Suspense fallback={<CircularProgress />}>
<App />
</Suspense>
</UserProvider> </UserProvider>
</ThemeProvider> </ThemeProvider>
</LocalizationProvider> </LocalizationProvider>
</Suspense>
</Router> </Router>
</React.StrictMode> </React.StrictMode>
) )
......
...@@ -10,10 +10,10 @@ function UserProvider(props: UserProviderProps) { ...@@ -10,10 +10,10 @@ function UserProvider(props: UserProviderProps) {
const { children } = props const { children } = props
const [user, setUser] = useState({ const [user, setUser] = useState({
auth: false, auth: false,
fetched: false,
first_name: '', first_name: '',
last_name: '', last_name: '',
}) })
const [fetching, setFetching] = useState(false)
const getUserInfo = async () => { const getUserInfo = async () => {
try { try {
...@@ -23,25 +23,26 @@ function UserProvider(props: UserProviderProps) { ...@@ -23,25 +23,26 @@ function UserProvider(props: UserProviderProps) {
if (response.ok) { if (response.ok) {
setUser({ setUser({
auth: true, auth: true,
fetched: true,
first_name: data.first_name, first_name: data.first_name,
last_name: data.last_name, last_name: data.last_name,
}) })
} else {
setUser({ auth: false, fetched: true, first_name: '', last_name: '' })
} }
} catch (error) { } catch (error) {
// Do nothing // Do nothing
} }
setFetching(false)
} }
const fetchUserInfo = () => { const fetchUserInfo = () => {
if (!user.auth && !fetching) { if (!user.auth && !user.fetched) {
setFetching(true)
getUserInfo() getUserInfo()
} }
} }
const clearUserInfo = () => { const clearUserInfo = () => {
setUser({ auth: false, first_name: '', last_name: '' }) setUser({ auth: false, fetched: false, first_name: '', last_name: '' })
} }
return ( return (
......
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { styled } from '@mui/material/styles' import { styled } from '@mui/material/styles'
import { Link } from '@mui/material'
import { import { reponsibleOrganization, responsibleOrganizationLink } from 'appConfig'
appStagingWarning,
appTechnicalSupportLink, import { getFooterLogo } from './logos'
reponsibleOrganization,
responsibleOrganizationLink,
} from 'appConfig'
import Link from 'components/link'
const FooterWrapper = styled('footer')(({ theme }) => ({ const FooterWrapper = styled('footer')(({ theme }) => ({
background: theme.greg.footerBackgroundColor, background: theme.greg.footerBackgroundColor,
height: 'fit-content', height: 'fit-content',
padding: '0rem 6.5rem', padding: '0rem 3.5rem',
marginTop: 'auto', marginTop: 'auto',
})) }))
const FooterSection = styled('section')({ const FooterSection = styled('section')({
header: { // header: {
marginBottom: '0.5rem', // marginBottom: '0.5rem',
fontSize: '1.5rem', // fontSize: '1.5rem',
}, // },
paddingLeft: '1rem', paddingLeft: '1rem',
}) })
const FooterSectionContent = styled('div')({
fontSize: '1rem',
paddingLeft: '0.3rem',
})
const ContentContainer = styled('div')(({ theme }) => ({ const ContentContainer = styled('div')(({ theme }) => ({
width: 'fit-content', // width: 'fit-content',
fontSize: '1.5rem',
color: theme.greg.footerTextColor, color: theme.greg.footerTextColor,
display: 'flex', display: 'flex',
flexWrap: 'nowrap', flexWrap: 'nowrap',
justifyContent: 'space-between', justifyContent: 'space-between',
margin: 'auto', // margin: '0 1rem',
padding: '1rem 1rem 1rem 0', padding: '1rem 0rem 1rem 0',
})) }))
const LogoContainer = styled('div')({
maxWidth: '30rem',
minWidth: '20rem',
})
const FooterHeader = styled('div')({
fontsize: '1rem',
})
const Footer: React.FunctionComponent = () => { const Footer: React.FunctionComponent = () => {
const { t } = useTranslation(['common', 'footer']) const { t } = useTranslation(['common', 'footer'])
return ( return (
<> <FooterWrapper>
<FooterWrapper> <ContentContainer>
<ContentContainer> <FooterSection>
<FooterSection> <LogoContainer>{getFooterLogo()}</LogoContainer>
<header>{t('footer:contactSectionHeader')}</header> </FooterSection>
<FooterSectionContent>
<Link
external
to={appTechnicalSupportLink}
inheritColor
underline
>
{t('footer:contactHelp')}
</Link>
</FooterSectionContent>
</FooterSection>
<FooterSection> <FooterSection sx={{ paddingTop: '1rem' }}>
<header>{t('footer:responsibleOrganizationHeader')}</header> <FooterHeader>
<FooterSectionContent> {t('footer:responsibleOrganizationHeader')}
<Link </FooterHeader>
external <Link
to={responsibleOrganizationLink} href={responsibleOrganizationLink}
inheritColor sx={{ color: 'white', fontSize: '1rem' }}
underline >
> {reponsibleOrganization}
{reponsibleOrganization} </Link>
</Link> </FooterSection>
</FooterSectionContent> </ContentContainer>
</FooterSection> </FooterWrapper>
</ContentContainer>
</FooterWrapper>
{appStagingWarning && <div className="alert">{t('staging')}</div>}
</>
) )
} }
......
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom' import { Link as RLink } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { styled } from '@mui/material/styles' import { styled } from '@mui/material/styles'
import Box from '@mui/material/Box' import { Box, Link } from '@mui/material'
import LogoutIcon from '@mui/icons-material/Logout'
import { useUserContext } from 'contexts'
import LogoBar from 'components/logobars/LogoBar'
import LanguageSelector from 'components/languageselector' import LanguageSelector from 'components/languageselector'
import UserInfo from 'routes/components/userInfo' import UserInfo from 'routes/components/userInfo'
import { getHeaderLogo } from './logos'
const MainWrapper = styled('div')(({ theme }) => ({ const StyledHeader = styled('header')(({ theme }) => ({
color: theme.greg.h1TextColor, color: theme.greg.h1TextColor,
backgroundColor: theme.greg.headerBackgroundColor, backgroundColor: theme.greg.headerBackgroundColor,
...@@ -21,66 +24,87 @@ const MainWrapper = styled('div')(({ theme }) => ({ ...@@ -21,66 +24,87 @@ const MainWrapper = styled('div')(({ theme }) => ({
}, },
})) }))
const Main = styled('div')(({ theme }) => ({ const MainContainer = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
margin: '0 auto', margin: '0 auto',
maxWidth: theme.greg.appMaxWidth, maxWidth: theme.greg.appMaxWidth,
padding: '0.5rem 6.5rem 1rem 6.5rem', padding: '1rem 6.5rem 1rem 6.5rem',
alignItems: 'flex-end',
})) }))
const Menu = styled('ul')({ const LogoContainer = styled('div')({
listStyleType: 'none', display: 'flex',
flexDirection: 'row',
alignItems: 'flex-end',
// paddingRight: '2rem',
}) })
const MenuItem = styled('div')({ const Menu = styled('div')({
display: 'flex',
// listStyleType: 'none',
flexDirection: 'row',
alignItems: 'flex-end',
fontSize: '1rem', fontSize: '1rem',
display: 'inline', paddingBottom: '0.5rem',
}) })
const Header = () => { const Header = () => {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const { user } = useUserContext()
return ( return (
<header> <StyledHeader>
<LogoBar /> <MainContainer>
<MainWrapper> <LogoContainer>
<Main>
<Box <Box
sx={{ sx={{
display: 'flex', paddingRight: '1.5rem',
flexDirection: 'column', width: '7rem',
paddingLeft: '3rem', // minWidth: '10rem',
}} }}
> >
<Link to="/"> {getHeaderLogo()}
<Box </Box>
<RLink to="/">
<Box
sx={{
color: 'white',
whiteSpace: 'nowrap',
textDecoration: 'none',
fontSize: '2rem',
fontWeight: 'bold',
}}
>
{t('header.applicationTitle')}
</Box>
</RLink>
</LogoContainer>
<Menu>
<LanguageSelector noPadding />
<UserInfo />
{user.auth && (
<Box sx={{ paddingLeft: '1rem' }}>
<Link
sx={{ sx={{
color: 'white', color: 'white',
whiteSpace: 'nowrap',
textDecoration: 'none',
fontSize: '2rem',
fontWeight: 'bold',
}} }}
href="/oidc/logout"
> >
{t('header.applicationTitle')} Logg ut
</Box> <LogoutIcon
</Link> fontSize="small"
<Box sx={{ fontSize: '1rem' }}> sx={{
{t('header.applicationDescription')} paddingLeft: '0.3rem',
verticalAlign: 'middle',
}}
/>
</Link>
</Box> </Box>
</Box> )}
<Menu> </Menu>
<MenuItem> </MainContainer>
<UserInfo /> </StyledHeader>
</MenuItem>
<MenuItem>
<LanguageSelector />
</MenuItem>
</Menu>
</Main>
</MainWrapper>
</header>
) )
} }
......
import { useTranslation } from 'react-i18next'
import { appInst } from 'appConfig'
import { ReactComponent as UiOLogoNo } from './uio-segl-full-neg-no.svg'
import { ReactComponent as UiOLogoEn } from './uio-segl-full-neg-en.svg'
import { ReactComponent as UiOAcronym } from './uio-logo-acronym-white.svg'
function getHeaderLogo() {
switch (appInst) {
case 'uio':
return <UiOAcronym />
case 'uib':
return <></>
default:
return <></>
}
}
function getFooterLogo() {
const { i18n } = useTranslation()
switch (appInst) {
case 'uio':
if (i18n.language === 'en') {
return <UiOLogoEn />
}
return <UiOLogoNo />
case 'uib':
return <></>
default:
return <></>
}
}
export { getHeaderLogo, getFooterLogo }
<?xml version="1.0" encoding="UTF-8"?>
<svg height="100%" width="100%" viewBox="0 0 128 56" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>04_UiO_forkortelse_NO_pos</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="04_UiO_forkortelse_NO_pos" fill="#ffffff" fill-rule="nonzero">
<g id="Group" transform="translate(0.000000, 4.000000)">
<path d="M0.99,2.8 L0.99,1.58 L23.77,1.58 L23.77,2.8 C17.17,3.62 17.04,4.43 17.04,11.57 L17.04,31.22 C17.04,41.89 20.64,47.88 29.89,47.88 C39.89,47.88 43.29,40.88 43.49,30.68 L43.49,11.5 C43.49,4.5 43.35,3.61 36.76,2.8 L36.76,1.58 L53.42,1.58 L53.42,2.8 C46.82,3.62 46.69,4.5 46.69,11.5 L46.69,30.54 C46.69,45.29 39.82,51.75 27.65,51.75 C14.12,51.75 7.73,44.75 7.73,32.17 L7.73,11.57 C7.73,4.57 7.59,3.62 0.99,2.8 Z" id="Path"></path>
<path d="M68.24,41.08 C68.24,48.08 68.31,49.04 73.68,49.65 L73.68,50.87 L54.23,50.87 L54.23,49.65 C59.6,49.04 59.67,48.09 59.67,41.08 L59.67,26.8 C59.67,19.59 59.19,19.52 54.23,19.39 L54.23,18.23 L68.44,13.67 C68.37,17.27 68.24,21.76 68.24,28.7 L68.24,41.08 Z" id="Path"></path>
<path d="M101.63,0.22 C114.96,0.22 127.54,9.2 127.54,25.99 C127.54,42.78 114.96,51.76 101.63,51.76 C88.3,51.76 75.72,42.78 75.72,25.99 C75.73,9.19 88.3,0.22 101.63,0.22 Z M101.63,2.73 C92.25,2.73 86.13,11.64 86.13,25.98 C86.13,40.33 92.25,49.23 101.63,49.23 C111.01,49.23 117.13,40.32 117.13,25.98 C117.13,11.64 111.01,2.73 101.63,2.73 Z" id="Shape"></path>
</g>
<path d="M57.98,5.51 C57.98,2.52 60.5,0.14 63.56,0.14 C66.69,0.14 69.14,2.52 69.14,5.51 C69.14,8.57 66.69,11.02 63.56,11.02 C60.49,11.02 57.98,8.57 57.98,5.51 Z" id="Path"></path>
</g>
</g>
</svg>
This diff is collapsed.
This diff is collapsed.
import { useEffect } from 'react' import { useEffect } from 'react'
import { Box } from '@mui/material'
import { useUserContext } from '../../contexts' import { useUserContext } from '../../contexts'
function UserInfo() { function UserInfo() {
...@@ -11,9 +13,9 @@ function UserInfo() { ...@@ -11,9 +13,9 @@ function UserInfo() {
if (user.auth) { if (user.auth) {
return ( return (
<div> <Box sx={{ paddingLeft: '0.5rem' }}>
{user.first_name} {user.last_name} {user.first_name} {user.last_name}
</div> </Box>
) )
} }
return <></> return <></>
......
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Box, CircularProgress } from '@mui/material'
import Page from 'components/page' import Page from 'components/page'
import { Debug } from 'components/debug' import { Debug } from 'components/debug'
...@@ -18,20 +19,36 @@ export default function FrontPage() { ...@@ -18,20 +19,36 @@ export default function FrontPage() {
{user.first_name} {user.last_name} {user.first_name} {user.last_name}
</p> </p>
<ul> <ul>
<li> <li key="root">
<Link to="/">Front page</Link> <Link to="/">Front page</Link>
</li> </li>
<li> <li key="sponsor">
<Link to="/sponsor/">Sponsor</Link> <Link to="/sponsor/">Sponsor</Link>
</li> </li>
<li> <li key="register">
<Link to="/register/">Registration</Link> <Link to="/register/">Registration</Link>
</li> </li>
<li key="registerwizard">
<Link to="/registerwizard/">Registration wizard</Link>
</li>
</ul> </ul>
</p> </p>
<Debug /> <Debug />
</Page> </Page>
) )
} }
if (!user.fetched) {
return (
<Box
sx={{
margin: 'auto',
}}
>
<CircularProgress />
</Box>
)
}
return <LoginPage /> return <LoginPage />
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment