diff --git a/frontend/.env b/frontend/.env index 60a2d2a264d628fd76716f27d74f22778ae43b99..bd56a6e4c209ad07a85513349a27e2f5161ef0f4 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,2 +1,11 @@ REACT_APP_VERSION=$npm_package_version REACT_APP_NAME=$npm_package_name + +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_THEME=default diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index fdf892b074b390c1ed1116c9a9e6c5e153d8791b..3e446301e09b80fec0560fad31d727e4f0e7d106 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -6,5 +6,12 @@ "language": { "change": "Change language to {{lang}}" }, - "loading": "Loading..." + "header": { + "applicationTitleShort": "GREG", + "applicationTitleLong": "Guest Registration", + "selectLanguage": "Select language" + }, + "loading": "Loading...", + "termsHeader": "Terms", + "staging": "Staging" } diff --git a/frontend/public/locales/en/footer.json b/frontend/public/locales/en/footer.json index 205afe095d14684e1f7f96bac62f68e5aed1292c..d052c9b6ffc6413c632c721e8e4f57d2470e9f33 100644 --- a/frontend/public/locales/en/footer.json +++ b/frontend/public/locales/en/footer.json @@ -1,3 +1,7 @@ { - "footerInfo": "This is a footer namespace value" + "footerInfo": "This is a footer namespace value", + "contactSectionHeader": "Need help?", + "contactHelp": "Contact", + "contactHelpMail": "Contact", + "responsibleOrganizationHeader": "Maintained by" } diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json index 96be51a3348df0f7451619613948c127d5441d10..887180ac7c0f29a071985584d002416fa659c419 100644 --- a/frontend/public/locales/nb/common.json +++ b/frontend/public/locales/nb/common.json @@ -6,5 +6,12 @@ "language": { "change": "Bytt språk til {{lang}}" }, - "loading": "Laster..." + "header":{ + "applicationTitleShort": "GREG", + "applicationTitleLong": "Gjesteregistrering", + "selectLanguage": "Velg språk" + }, + "loading": "Laster...", + "termsHeader": "Vilkår", + "staging": "Staging" } diff --git a/frontend/public/locales/nb/footer.json b/frontend/public/locales/nb/footer.json index cb74ed15090a08f18e35249cd3e900c64a8d8c6c..436d90c7f38d75465ee6db23ba0202b7e93e7c6f 100644 --- a/frontend/public/locales/nb/footer.json +++ b/frontend/public/locales/nb/footer.json @@ -1,3 +1,7 @@ { - "footerInfo": "Verdi i namespace footer" + "footerInfo": "Verdi i namespace footer", + "contactSectionHeader": "Trenger du hjelp?", + "contactHelp": "Kontakt", + "contactHelpMail": "Kontaktveileder", + "responsibleOrganizationHeader": "Ansvarlig for tjenesten" } diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json index 9da0352ce402851e72b564f51b8c909428f57937..92c11d0febbfabda6240752d39f1573cbd218a8e 100644 --- a/frontend/public/locales/nn/common.json +++ b/frontend/public/locales/nn/common.json @@ -4,7 +4,15 @@ "test": "Dette er ein 'nested' verdi" }, "language": { + "languageName": "Språk", "change": "Bytt språk til {{lang}}" }, - "loading": "Lastar..." + "header":{ + "applicationTitleShort": "GREG", + "applicationTitleLong": "Gjesteregistrering", + "selectLanguage": "Velg språk" + }, + "loading": "Lastar...", + "termsHeader": "Vilkår", + "staging": "Staging" } diff --git a/frontend/public/locales/nn/footer.json b/frontend/public/locales/nn/footer.json index cb74ed15090a08f18e35249cd3e900c64a8d8c6c..ce248c21dd6ff9285f77f8c4dbde08303ba943e3 100644 --- a/frontend/public/locales/nn/footer.json +++ b/frontend/public/locales/nn/footer.json @@ -1,3 +1,7 @@ { - "footerInfo": "Verdi i namespace footer" + "footerInfo": "Verdi i namespace footer", + "contactSectionHeader": "Trenger du hjelp?", + "contactHelp": "Kontakt", + "contactHelpMail": "Kontaktrettleiar", + "responsibleOrganizationHeader": "Ansvarleg for tenesta" } diff --git a/frontend/public/uio/uio-app-logo-en.png b/frontend/public/uio/uio-app-logo-en.png new file mode 100644 index 0000000000000000000000000000000000000000..fa5c8e2e2d02c81ba3554a2aca6e56427991275f Binary files /dev/null and b/frontend/public/uio/uio-app-logo-en.png differ diff --git a/frontend/public/uio/uio-app-logo-nb.png b/frontend/public/uio/uio-app-logo-nb.png new file mode 100644 index 0000000000000000000000000000000000000000..478f41b5abd9b52ed8b8dfb06ce0f46651e5353c Binary files /dev/null and b/frontend/public/uio/uio-app-logo-nb.png differ diff --git a/frontend/src/appConfig.ts b/frontend/src/appConfig.ts index ea76eda4eccb697485a4299896781824d1b78438..84880d70a088ae516f630454601f05d9eb61b823 100644 --- a/frontend/src/appConfig.ts +++ b/frontend/src/appConfig.ts @@ -1,8 +1,8 @@ declare global { - /* tslint:disable */ - interface Window { - ENV: any - } + /* tslint:disable */ + interface Window { + ENV: any + } } /* Locate the client environment */ @@ -16,7 +16,19 @@ export const appTimezone: string = 'Europe/Oslo' export const appVersion: string = process.env.REACT_APP_VERSION as string export const appName: string = process.env.REACT_APP_NAME as string +/* Institution */ +export const appInst: string = env.REACT_APP_INST as string + /* Theming */ export const appTheme: string = env.REACT_APP_THEME - ? (env.REACT_APP_THEME as string) - : 'default' + ? (env.REACT_APP_THEME as string) + : 'default' + +/* Show warning in the footer about this being a staging/test system */ +export const appStagingWarning: boolean = + env.REACT_APP_STAGING_WARNING === 'true' + +/* Footer content */ +export const appTechnicalSupportLink: string = env.REACT_APP_SUPPORT_URL as string +export const reponsibleOrganization: string = env.REACT_APP_RESPONSIBLE_ORGANIZATION as string +export const responsibleOrganizationLink: string = env.REACT_APP_RESPONSIBLE_ORGANIZATION_LINK as string diff --git a/frontend/src/components/dropdownmenu/index.tsx b/frontend/src/components/dropdownmenu/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fbdbd36feb4135114f5c8be6d5260d3ad8c68013 --- /dev/null +++ b/frontend/src/components/dropdownmenu/index.tsx @@ -0,0 +1,131 @@ +import React, { useState } from 'react' +import styled from 'styled-components/macro' + + +const DropDownMenu = styled.div` + position: relative; + + &:hover { + cursor: pointer; + } + + width: fit-content; + display: inline-block; +` + +const DropDownList = styled.ul` + position: absolute; + right: 1rem; + top: 2rem; + border-radius: 1rem; + list-style-type: none; + min-width: 10rem; + max-height: 50rem; + z-index: 100; + background: ${({ theme }) => theme.colors.dropDownMenuBackground}; +` + +interface IDropDownOption { + name: string; + value: any; +} + +const getValueName = (value: any, options: Array<IDropDownOption>): string => { + for (let i = 0; i < options.length; i += 1) { + if (options[i].value === value) { + return options[i].name + } + } + return '' +} + +interface IProps { + options: any[]; + placeholder?: string; + value: any; + onChange: (event: any) => void; +} + + +function DropDown(props: IProps) { + const [open, setOpen] = useState(false) + + const { + options, + placeholder, + value, + onChange, + } = props + + function handleClick(option: any) { + setOpen(!open) + onChange(option.value) + } + + function openMenu() { + if (!open) { + setOpen(true) + } + } + + return ( + + <DropDownMenu + aria-haspopup='true' + aria-expanded={open} + /* eslint-disable-next-line react/jsx-no-bind */ + onFocus={openMenu} + tabIndex={0} + > + <div> + <div role='button' + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + openMenu() + } + }} + onClick={openMenu} + tabIndex={0} + > + {/* eslint-disable-next-line */} + {placeholder + ? placeholder + : getValueName(value, options).toLowerCase()} + </div> + </div> + <div> + {open && ( + <DropDownList> + {options.map((option) => ( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions + <li + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + openMenu() + } + }} + + onClick={( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + event: React.MouseEvent<HTMLElement, MouseEvent>, + ) => { + handleClick(option) + }} + > + <p>{option.name}</p> + </li> + ))} + </DropDownList> + )} + </div> + </DropDownMenu> + ) +} + + +DropDown.defaultProps = + { + placeholder: undefined, + } + +export default DropDown diff --git a/frontend/src/components/languageselector/index.tsx b/frontend/src/components/languageselector/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..575b56d7904b1558c685b9b4556a29b1c76b65e8 --- /dev/null +++ b/frontend/src/components/languageselector/index.tsx @@ -0,0 +1,38 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import DropDown from '../dropdownmenu' + + +function LanguageSelector() { + const { i18n, t } = useTranslation('common') + + const options = [ + { + name: 'English', + value: 'en', + }, + { + name: 'Norsk nynorsk', + value: 'nn', + }, + { + name: 'Norsk bokmål', + value: 'nb', + }, + ] + + + return ( + <DropDown options={options} + placeholder={t('header.selectLanguage')} + value={options[2]} + onChange={newValue => { + i18n.changeLanguage(newValue) + } + } + /> + ) +} + + +export default LanguageSelector diff --git a/frontend/src/components/logobars/LogoBar.tsx b/frontend/src/components/logobars/LogoBar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2af512f339f8742d8428050f52ea4753ea82c047 --- /dev/null +++ b/frontend/src/components/logobars/LogoBar.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { appTheme } from 'appConfig' +import UiOLogoBar from './UiO' +import UiBLogoBar from './UiB' + + +function LogoBar() { + switch (appTheme) { + case 'uio': + return <UiOLogoBar /> + case 'uib': + return <UiBLogoBar /> + default: + return <UiOLogoBar /> + } +} + +export default LogoBar \ No newline at end of file diff --git a/frontend/src/components/logobars/UiB.tsx b/frontend/src/components/logobars/UiB.tsx new file mode 100644 index 0000000000000000000000000000000000000000..059ac0d05a1f779ac73565cfba4937b26d3d246c --- /dev/null +++ b/frontend/src/components/logobars/UiB.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import styled from 'styled-components/macro' + + +const LogoBarWrapper = styled.div` + background-color: ${(props) => props.theme.colors.secondary}); +` + +const LogoBar = styled.div` + margin: 0 auto; + max-width: ${({ theme }) => theme.appMaxWidth}); + padding: 0 ${({ theme }) => theme.horizontalMdPadding}); +` + +function UiBLogoBar() { + return ( + <LogoBarWrapper> + <LogoBar> + Insert UiB logo here + </LogoBar> + </LogoBarWrapper> + ) +} + +export default UiBLogoBar diff --git a/frontend/src/components/logobars/UiO.tsx b/frontend/src/components/logobars/UiO.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8796ac584d1d430e2be16ad14e6a10c0b8f32dc3 --- /dev/null +++ b/frontend/src/components/logobars/UiO.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import styled from 'styled-components/macro' + +import { useTranslation } from 'react-i18next' + + +const LogoBarWrapper = styled.div` + display: flex; + justify-content: center; + background-color: ${({ theme }) => theme.page.headerBackgroundColor}; +` + +type Language = { + language: string +} + +const Logo = styled.div<Language>` + background: ${props => props.language === 'en' ? 'url("/uio/uio-app-logo-en.png") left center no-repeat' : 'url("/uio/uio-app-logo-nb.png") left center no-repeat'}; + min-width: 20rem; + height: 2rem; +` + +function UiOLogoBar() { + const { i18n } = useTranslation(['common', 'footer']) + + return ( + <LogoBarWrapper> + <Logo language={i18n.language} /> + </LogoBarWrapper> + ) +} + +export default UiOLogoBar diff --git a/frontend/src/routes/components/footer.tsx b/frontend/src/routes/components/footer.tsx index b36a7433dc291149b7100c5452e142bfb26d9204..f6821a56c801a7ae8588bbb35b3c1dc9c8f0306d 100644 --- a/frontend/src/routes/components/footer.tsx +++ b/frontend/src/routes/components/footer.tsx @@ -1,27 +1,91 @@ import React from 'react' import styled from 'styled-components/macro' +import { useTranslation } from 'react-i18next' +import { + appStagingWarning, + appTechnicalSupportLink, + reponsibleOrganization, responsibleOrganizationLink, +} from '../../appConfig' +import Link from '../../components/link' -// Placeholder footer component -const StyledFooter = styled.footer` +const FooterWrapper = styled.footer` background: ${({ theme }) => theme.footer.backgroundColor}; - padding: 4rem 0; + height: fit-content; + padding: 0rem ${({ theme }) => theme.horizontalPadding}; margin-top: auto; ` -const FooterWrapper = styled.div` - background: ${({ theme }) => theme.footer.backgroundColor}; - color: white; - height: fit-content; - max-width: ${({ theme }) => theme.appMaxWidth}; - margin: 0 auto; - padding: 0rem ${({ theme }) => theme.horizontalPadding}; +const FooterSection = styled.section` + header { + margin-bottom: 0.5rem; + font-size: 1.5rem; + } + + padding-left: 1rem; +` + + +const FooterSectionContent = styled.div` + font-size: 1rem; + padding-left: 0.3rem; +` + +const ContentContainer = styled.div` + width: fit-content; + color: ${({ theme }) => theme.footerTextColor}; + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + margin: auto; + padding-top: 1rem; + padding-right: 1rem; + padding-bottom: 1rem; ` -export default function Footer() { +const Footer: React.FunctionComponent = () => { + const { t } = useTranslation(['common', 'footer']) + return ( - <StyledFooter> - <FooterWrapper>Footer 123</FooterWrapper> - </StyledFooter> + <> + <FooterWrapper> + <ContentContainer> + <FooterSection> + <header>{t('footer:contactSectionHeader')}</header> + <FooterSectionContent> + <Link + external + to={appTechnicalSupportLink} + inheritColor + underline + > + {t('footer:contactHelp')} + </Link> + </FooterSectionContent> + </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> + )} + </> ) } + +export default Footer diff --git a/frontend/src/routes/components/header.tsx b/frontend/src/routes/components/header.tsx index b297d552d8ed737f12652fc4623c20b4a58095d3..0a4f62cbdfa3e2ff31d8a8d7a05155ed1abf5361 100644 --- a/frontend/src/routes/components/header.tsx +++ b/frontend/src/routes/components/header.tsx @@ -1,45 +1,67 @@ import React from 'react' import styled from 'styled-components/macro' +import { useTranslation } from 'react-i18next' +import LogoBar from '../../components/logobars/LogoBar' +import LanguageSelector from '../../components/languageselector' -// Placeholder header component! const MainWrapper = styled.div` - background-color: green; + color: ${({ theme }) => theme.page.headerColor}; + background-color: ${({ theme }) => theme.page.headerBackgroundColor}; ` const Main = styled.div` + display: flex; + justify-content: space-between; margin: 0 auto; - max-width: ${({ theme }) => theme.maxWidth}; - height: ${({ theme }) => theme.header.height}; - padding: 2.5rem ${({ theme }) => theme.horizontalPadding} 3rem - ${({ theme }) => theme.horizontalPadding}; + max-width: ${props => props.theme.appMaxWidth}; + padding: ${props => `0.5rem ${props.theme.horizontalPadding} 1rem ${props.theme.horizontalPadding}`}; +` + +const Menu = styled.ul` + list-style-type: none; +` + +const MenuItem = styled.div` + font-size: 1rem; + display: inline; ` -const MainRow = styled.div` +const TitleBox = styled.div` display: flex; - justify-content: space-between; + flex-direction: column; + padding-left: 3rem; ` -const PageTitle = styled.div` - color: blue; +const ShortTitle = styled.div` + font-size: 2rem; ` -const H2 = styled.h2` - font-size: 2.8rem; - line-height: 4.5rem; + +const LongTitle = styled.div` + font-size: 1rem; ` -export default function Header() { +function Header() { + const { t } = useTranslation('common') + return ( <header> + <LogoBar /> <MainWrapper> <Main> - <MainRow> - <PageTitle> - <H2>Greg!</H2> - </PageTitle> - </MainRow> + <TitleBox> + <ShortTitle>{t('header.applicationTitleShort')}</ShortTitle> + <LongTitle>{t('header.applicationTitleLong')}</LongTitle> + </TitleBox> + <Menu> + <MenuItem> + <LanguageSelector/> + </MenuItem> + </Menu> </Main> </MainWrapper> </header> ) } + +export default Header diff --git a/frontend/src/styled.d.ts b/frontend/src/styled.d.ts index a91823417eaed6818da06451516dbc9dd07a8c78..7031296ac88d87b9ff9da09ddd7a0c4d7bfcc3f4 100644 --- a/frontend/src/styled.d.ts +++ b/frontend/src/styled.d.ts @@ -7,6 +7,7 @@ declare module 'styled-components' { colors: { main: string secondary: string + dropDownMenuBackground: string } footer: { backgroundColor: string @@ -17,9 +18,14 @@ declare module 'styled-components' { horizontalPadding: string linkExternalColor: string linkInternalColor: string + footerBackgroundColor: string + footerTextColor: string + footerJustifyContent: string + horizontalMdPadding: string maxWidth: string page: { headerColor: string + headerBackgroundColor: string horizontalPadding: string } } diff --git a/frontend/src/themes/color.ts b/frontend/src/themes/color.ts new file mode 100644 index 0000000000000000000000000000000000000000..d13e0f21856f5fa36dce9942881c5289cf1d0b45 --- /dev/null +++ b/frontend/src/themes/color.ts @@ -0,0 +1,10 @@ + +export module Color { + export const white = '#FFFFFF' + export const hotPink = '#FF69B4' + export const blueish = '#2771bb' + export const lightOliveGreen = '#91BD60' + export const black = '#000000' + export const darkGray = '#2D2D2E' + export const lighterBlack = '#282c34' +} diff --git a/frontend/src/themes/main.ts b/frontend/src/themes/main.ts index e03827205a038806e78c27605a3877ff81869820..8af960ea1c0c06ecbcf1b84c2efd321980353f78 100644 --- a/frontend/src/themes/main.ts +++ b/frontend/src/themes/main.ts @@ -6,6 +6,7 @@ const mainTheme: DefaultTheme = { colors: { main: 'hotPink', secondary: 'white', + dropDownMenuBackground: 'grey' }, footer: { backgroundColor: 'black', @@ -17,8 +18,13 @@ const mainTheme: DefaultTheme = { linkInternalColor: 'white', linkExternalColor: 'blueish', maxWidth: '110rem', + footerBackgroundColor: 'green', + footerTextColor: 'white', + footerJustifyContent: 'flex-end', + horizontalMdPadding: '6.5rem', page: { - headerColor: 'grey', + headerColor: 'white', + headerBackgroundColor: 'black', horizontalPadding: '0rem', }, } diff --git a/frontend/src/themes/uib.ts b/frontend/src/themes/uib.ts index 0cd69cd5732a76e9f1a4873916a17536c4de9c51..2d272789dcaba43a0569514d6d72041c38f75a25 100644 --- a/frontend/src/themes/uib.ts +++ b/frontend/src/themes/uib.ts @@ -2,6 +2,7 @@ const uibTheme = { colors: { main: 'hotPink', secondary: 'black', + dropDownMenuBackground: 'grey' }, } diff --git a/frontend/src/themes/uio.ts b/frontend/src/themes/uio.ts index 6481aeb85e9e5a362322625834cdcabf836b9c06..6f0f9a1c4c4893660ae49153eda119c888d684cf 100644 --- a/frontend/src/themes/uio.ts +++ b/frontend/src/themes/uio.ts @@ -2,6 +2,7 @@ const uioTheme = { colors: { main: 'hotPink', secondary: 'white', + dropDownMenuBackground: 'grey' }, }