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
REACT_APP_INST=uio
REACT_APP_SUPPORT_URL=https://example.org
REACT_APP_RESPONSIBLE_ORGANIZATION=N/A
REACT_APP_RESPONSIBLE_ORGANIZATION_LINK=https://example.org
REACT_APP_RESPONSIBLE_ORGANIZATION='Seksjon for integrasjon og elektroniske identiteter (INT)'
REACT_APP_RESPONSIBLE_ORGANIZATION_LINK='https://www.usit.uio.no/om/organisasjon/bnt/usitint/'
REACT_APP_THEME=default
......@@ -12,7 +12,7 @@
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"project": "./frontend/tsconfig.json",
"ecmaFeatures": {
"jsx": true
},
......
......@@ -8,7 +8,7 @@
},
"fnr": "Fødselsnummer",
"header": {
"applicationTitle": "Gjesteregistrering",
"applicationTitle": "Gjestetjenesten",
"applicationDescription": "Registreringstjeneste for gjester",
"selectLanguage": "Velg språk"
},
......
......@@ -3,5 +3,5 @@
"contactSectionHeader": "Trenger du hjelp?",
"contactHelp": "Kontakt",
"contactHelpMail": "Kontaktveileder",
"responsibleOrganizationHeader": "Ansvarlig for tjenesten"
"responsibleOrganizationHeader": "Ansvarlig for denne tjenesten"
}
......@@ -9,7 +9,7 @@
},
"fnr": "National identity number",
"header": {
"applicationTitle": "Gjesteregistrering",
"applicationTitle": "Gjestetenesta",
"applicationDescription": "Registreringsteneste for gjester",
"selectLanguage": "Velg språk"
},
......
......@@ -3,5 +3,5 @@
"contactSectionHeader": "Treng du hjelp?",
"contactHelp": "Kontakt",
"contactHelpMail": "Kontaktrettleiar",
"responsibleOrganizationHeader": "Ansvarleg for tenesta"
"responsibleOrganizationHeader": "Ansvarleg for denne tenesta"
}
......@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { MenuItem, Select, SelectChangeEvent } from '@mui/material'
function LanguageSelector() {
function LanguageSelector({ noPadding }: { noPadding: boolean }) {
const { i18n, t } = useTranslation('common')
const [selectedLanguage, setSelectedLanguage] = useState('en')
......@@ -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 (
<Select
labelId="language-select"
label={t('header.selectLanguage')}
sx={{
color: 'white',
'& .MuiSelect-icon': {
color: 'white',
},
}}
defaultValue={selectedLanguage}
sx={sx}
variant="standard"
value={selectedLanguage}
onChange={handleLanguageChange}
>
{Array.from(languageOptions.entries()).map((entry) => (
<MenuItem value={entry[0]}>{entry[1]}</MenuItem>
<MenuItem key={entry[0]} value={entry[0]}>
{entry[1]}
</MenuItem>
))}
</Select>
)
......
import React from 'react'
import React, { Suspense } from 'react'
import { useTranslation } from 'react-i18next'
import Spinner from 'components/animations/spinner'
......
......@@ -2,6 +2,7 @@ import { createContext, useContext } from 'react'
interface User {
auth: boolean
fetched: boolean
first_name: string
last_name: string
}
......@@ -15,7 +16,7 @@ interface IUserContext {
function noop() {}
export const UserContext = createContext<IUserContext>({
user: { auth: false, first_name: '', last_name: '' },
user: { auth: false, fetched: false, first_name: '', last_name: '' },
fetchUserInfo: noop,
clearUserInfo: noop,
})
......
......@@ -21,9 +21,9 @@ i18n
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
// react: {
// useSuspense: false,
// },
detection: {
order: ['cookie'],
caches: ['cookie'],
......
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom'
import { ThemeProvider } from '@mui/material/styles'
import { CircularProgress } from '@mui/material'
import AdapterDateFns from '@mui/lab/AdapterDateFns'
import { LocalizationProvider } from '@mui/lab'
import { BrowserRouter as Router } from 'react-router-dom'
......@@ -16,15 +17,15 @@ function appRoot() {
return (
<React.StrictMode>
<Router>
<Suspense fallback={<Loading />}>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<ThemeProvider theme={getTheme()}>
<UserProvider>
<App />
<Suspense fallback={<CircularProgress />}>
<App />
</Suspense>
</UserProvider>
</ThemeProvider>
</LocalizationProvider>
</Suspense>
</LocalizationProvider>
</Router>
</React.StrictMode>
)
......
......@@ -10,10 +10,10 @@ function UserProvider(props: UserProviderProps) {
const { children } = props
const [user, setUser] = useState({
auth: false,
fetched: false,
first_name: '',
last_name: '',
})
const [fetching, setFetching] = useState(false)
const getUserInfo = async () => {
try {
......@@ -23,25 +23,26 @@ function UserProvider(props: UserProviderProps) {
if (response.ok) {
setUser({
auth: true,
fetched: true,
first_name: data.first_name,
last_name: data.last_name,
})
} else {
setUser({ auth: false, fetched: true, first_name: '', last_name: '' })
}
} catch (error) {
// Do nothing
}
setFetching(false)
}
const fetchUserInfo = () => {
if (!user.auth && !fetching) {
setFetching(true)
if (!user.auth && !user.fetched) {
getUserInfo()
}
}
const clearUserInfo = () => {
setUser({ auth: false, first_name: '', last_name: '' })
setUser({ auth: false, fetched: false, first_name: '', last_name: '' })
}
return (
......
import React from 'react'
import { useTranslation } from 'react-i18next'
import { styled } from '@mui/material/styles'
import { Link } from '@mui/material'
import {
appStagingWarning,
appTechnicalSupportLink,
reponsibleOrganization,
responsibleOrganizationLink,
} from 'appConfig'
import Link from 'components/link'
import { reponsibleOrganization, responsibleOrganizationLink } from 'appConfig'
import { getFooterLogo } from './logos'
const FooterWrapper = styled('footer')(({ theme }) => ({
background: theme.greg.footerBackgroundColor,
height: 'fit-content',
padding: '0rem 6.5rem',
padding: '0rem 3.5rem',
marginTop: 'auto',
}))
const FooterSection = styled('section')({
header: {
marginBottom: '0.5rem',
fontSize: '1.5rem',
},
// header: {
// marginBottom: '0.5rem',
// fontSize: '1.5rem',
// },
paddingLeft: '1rem',
})
const FooterSectionContent = styled('div')({
fontSize: '1rem',
paddingLeft: '0.3rem',
})
const ContentContainer = styled('div')(({ theme }) => ({
width: 'fit-content',
// width: 'fit-content',
fontSize: '1.5rem',
color: theme.greg.footerTextColor,
display: 'flex',
flexWrap: 'nowrap',
justifyContent: 'space-between',
margin: 'auto',
padding: '1rem 1rem 1rem 0',
// margin: '0 1rem',
padding: '1rem 0rem 1rem 0',
}))
const LogoContainer = styled('div')({
maxWidth: '30rem',
minWidth: '20rem',
})
const FooterHeader = styled('div')({
fontsize: '1rem',
})
const Footer: React.FunctionComponent = () => {
const { t } = useTranslation(['common', 'footer'])
return (
<>
<FooterWrapper>
<ContentContainer>
<FooterSection>
<header>{t('footer:contactSectionHeader')}</header>
<FooterSectionContent>
<Link
external
to={appTechnicalSupportLink}
inheritColor
underline
>
{t('footer:contactHelp')}
</Link>
</FooterSectionContent>
</FooterSection>
<FooterWrapper>
<ContentContainer>
<FooterSection>
<LogoContainer>{getFooterLogo()}</LogoContainer>
</FooterSection>
<FooterSection>
<header>{t('footer:responsibleOrganizationHeader')}</header>
<FooterSectionContent>
<Link
external
to={responsibleOrganizationLink}
inheritColor
underline
>
{reponsibleOrganization}
</Link>
</FooterSectionContent>
</FooterSection>
</ContentContainer>
</FooterWrapper>
{appStagingWarning && <div className="alert">{t('staging')}</div>}
</>
<FooterSection sx={{ paddingTop: '1rem' }}>
<FooterHeader>
{t('footer:responsibleOrganizationHeader')}
</FooterHeader>
<Link
href={responsibleOrganizationLink}
sx={{ color: 'white', fontSize: '1rem' }}
>
{reponsibleOrganization}
</Link>
</FooterSection>
</ContentContainer>
</FooterWrapper>
)
}
......
import React from 'react'
import { Link } from 'react-router-dom'
import { Link as RLink } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
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 UserInfo from 'routes/components/userInfo'
import { getHeaderLogo } from './logos'
const MainWrapper = styled('div')(({ theme }) => ({
const StyledHeader = styled('header')(({ theme }) => ({
color: theme.greg.h1TextColor,
backgroundColor: theme.greg.headerBackgroundColor,
......@@ -21,66 +24,87 @@ const MainWrapper = styled('div')(({ theme }) => ({
},
}))
const Main = styled('div')(({ theme }) => ({
const MainContainer = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
margin: '0 auto',
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')({
listStyleType: 'none',
const LogoContainer = styled('div')({
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',
display: 'inline',
paddingBottom: '0.5rem',
})
const Header = () => {
const { t } = useTranslation('common')
const { user } = useUserContext()
return (
<header>
<LogoBar />
<MainWrapper>
<Main>
<StyledHeader>
<MainContainer>
<LogoContainer>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
paddingLeft: '3rem',
paddingRight: '1.5rem',
width: '7rem',
// minWidth: '10rem',
}}
>
<Link to="/">
<Box
{getHeaderLogo()}
</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={{
color: 'white',
whiteSpace: 'nowrap',
textDecoration: 'none',
fontSize: '2rem',
fontWeight: 'bold',
}}
href="/oidc/logout"
>
{t('header.applicationTitle')}
</Box>
</Link>
<Box sx={{ fontSize: '1rem' }}>
{t('header.applicationDescription')}
Logg ut
<LogoutIcon
fontSize="small"
sx={{
paddingLeft: '0.3rem',
verticalAlign: 'middle',
}}
/>
</Link>
</Box>
</Box>
<Menu>
<MenuItem>
<UserInfo />
</MenuItem>
<MenuItem>
<LanguageSelector />
</MenuItem>
</Menu>
</Main>
</MainWrapper>
</header>
)}
</Menu>
</MainContainer>
</StyledHeader>
)
}
......
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 { Box } from '@mui/material'
import { useUserContext } from '../../contexts'
function UserInfo() {
......@@ -11,9 +13,9 @@ function UserInfo() {
if (user.auth) {
return (
<div>
<Box sx={{ paddingLeft: '0.5rem' }}>
{user.first_name} {user.last_name}
</div>
</Box>
)
}
return <></>
......
import { Link } from 'react-router-dom'
import { Box, CircularProgress } from '@mui/material'
import Page from 'components/page'
import { Debug } from 'components/debug'
......@@ -18,20 +19,36 @@ export default function FrontPage() {
{user.first_name} {user.last_name}
</p>
<ul>
<li>
<li key="root">
<Link to="/">Front page</Link>
</li>
<li>
<li key="sponsor">
<Link to="/sponsor/">Sponsor</Link>
</li>
<li>
<li key="register">
<Link to="/register/">Registration</Link>
</li>
<li key="registerwizard">
<Link to="/registerwizard/">Registration wizard</Link>
</li>
</ul>
</p>
<Debug />
</Page>
)
}
if (!user.fetched) {
return (
<Box
sx={{
margin: 'auto',
}}
>
<CircularProgress />
</Box>
)
}
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