Skip to content
Snippets Groups Projects
Verified Commit 78b9cae9 authored by Andreas Ellewsen's avatar Andreas Ellewsen
Browse files

Merge remote-tracking branch 'origin/master' into GREG-48-validatefnr

parents ff2b8093 f4c7db34
No related branches found
No related tags found
1 merge request!57Add fnr validation component
Pipeline #93829 failed
Showing
with 5645 additions and 4525 deletions
......@@ -6,16 +6,55 @@ variables:
cache:
paths:
- .cache/pip
- venv/
- venv/ # makes the virtual env available for jobs
before_script:
- python -V
stages:
- venv update # Keep the cached virtual env up to date with dependencies
- tests and linting # Run test/linting for frontend and backend
test:
update:
before_script:
- python -V
image: python:3.9-buster
stage: venv update
script:
- make install
backend lint:
before_script:
- python -V
image: python:3.9-buster
stage: tests and linting
script:
- make lint
backend test:
before_script:
- python -V
image: python:3.9-buster
stage: tests and linting
script:
- make test
artifacts:
reports:
cobertura: coverage.xml
frontend test:
image: node:14-alpine
stage: tests and linting
script:
- cd frontend
- npm install -g npm@latest
- npm ci
- npm run coverage:ci
coverage:
'/All files[^|]*\|[^|]*\s+([\d\.]+)/'
frontend lint:
image: node:14-alpine
stage: tests and linting
script:
- cd frontend
- npm install -g npm@latest
- npm ci
- npm run lint
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
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"plugin:react/recommended",
"airbnb",
"airbnb-typescript",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint"],
"rules": {
"semi": "off",
"@typescript-eslint/semi": ["error", "never"]
}
}
......@@ -11,6 +11,9 @@
# production
/build
# frontend configuration
/public/env.js
# misc
.DS_Store
.env.local
......
This diff is collapsed.
......@@ -27,11 +27,16 @@
"web-vitals": "^1.1.2"
},
"scripts": {
"start": "NODE_ENV=development HOST=0.0.0.0 npm run build:env && react-scripts start",
"build": "npm run build:env && react-scripts build",
"start": "NODE_ENV=development HOST=0.0.0.0 npm run build:env && DISABLE_ESLINT_PLUGIN='true' react-scripts start",
"build": "npm run build:env && DISABLE_ESLINT_PLUGIN='true' react-scripts build",
"build:env": "node scripts/build-env.js",
"test": "react-scripts test --env=jsdom",
"test:ci": "react-scripts test --env=jsdom --ci --reporters=default --reporters=jest-junit --coverageReporters=cobertura --coverage --coverageDirectory=coverage",
"coverage:ci": "react-scripts test --env=jsdom --ci --coverage --color --watchAll=false",
"coverage": "react-scripts test --env=jsdom --coverage --color",
"lint": "eslint --ext js,jsx,ts,tsx src",
"lint-report": "eslint --ext js,jsx,ts,tsx -f checkstyle -o checkstyle-result.xml src",
"clean": "rm -rf node_modules",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
......@@ -54,6 +59,17 @@
},
"devDependencies": {
"@types/react-helmet": "^6.1.2",
"@types/react-router-dom": "^5.1.8"
"@types/react-router-dom": "^5.1.8",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-typescript": "^14.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest-junit": "^12.2.0"
}
}
window.ENV = {
NODE_ENV: 'development',
PUBLIC_URL: '',
FAST_REFRESH: true,
REACT_APP_VERSION: '0.1.0',
REACT_APP_NAME: 'greg',
}
......@@ -7,5 +7,12 @@
"change": "Change language to {{lang}}"
},
"fnr": "National identity number",
"loading": "Loading..."
"header": {
"applicationTitleShort": "GREG",
"applicationTitleLong": "Guest Registration",
"selectLanguage": "Select language"
},
"loading": "Loading...",
"termsHeader": "Terms",
"staging": "Staging"
}
{
"footerInfo": "This is a footer namespace value"
"footerInfo": "This is a footer namespace value",
"contactSectionHeader": "Need help?",
"contactHelp": "Contact",
"contactHelpMail": "Contact",
"responsibleOrganizationHeader": "Maintained by"
}
......@@ -7,5 +7,12 @@
"change": "Bytt språk til {{lang}}"
},
"fnr": "Fødselsnummer",
"loading": "Laster..."
"header":{
"applicationTitleShort": "GREG",
"applicationTitleLong": "Gjesteregistrering",
"selectLanguage": "Velg språk"
},
"loading": "Laster...",
"termsHeader": "Vilkår",
"staging": "Staging"
}
{
"footerInfo": "Verdi i namespace footer"
"footerInfo": "Verdi i namespace footer",
"contactSectionHeader": "Trenger du hjelp?",
"contactHelp": "Kontakt",
"contactHelpMail": "Kontaktveileder",
"responsibleOrganizationHeader": "Ansvarlig for tjenesten"
}
......@@ -4,8 +4,16 @@
"test": "Dette er ein 'nested' verdi"
},
"language": {
"languageName": "Språk",
"change": "Bytt språk til {{lang}}"
},
"fnr": "National identity number",
"loading": "Lastar..."
"header":{
"applicationTitleShort": "GREG",
"applicationTitleLong": "Gjesteregistrering",
"selectLanguage": "Velg språk"
},
"loading": "Lastar...",
"termsHeader": "Vilkår",
"staging": "Staging"
}
{
"footerInfo": "Verdi i namespace footer"
"footerInfo": "Verdi i namespace footer",
"contactSectionHeader": "Trenger du hjelp?",
"contactHelp": "Kontakt",
"contactHelpMail": "Kontaktrettleiar",
"responsibleOrganizationHeader": "Ansvarleg for tenesta"
}
frontend/public/uio/uio-app-logo-en.png

5.14 KiB

frontend/public/uio/uio-app-logo-nb.png

4.81 KiB

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
......@@ -15,3 +15,4 @@ export const Button = styled.a`
}
background: ${({ theme }) => theme.colors.main};
`
export default Button
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
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
import styled, {css, DefaultTheme} from 'styled-components/macro'
import React from 'react'
import styled, { css } from 'styled-components/macro'
interface IStyledLink {
theme: DefaultTheme
external?: boolean
underline?: boolean
marginRight?: boolean
inheritColor?: boolean
noUnderline?: boolean
external?: boolean
underline?: boolean
marginRight?: boolean
inheritColor?: boolean
noUnderline?: boolean
}
......@@ -32,25 +31,28 @@ const externalLinkIcon = (
d="M16.807.19a.596.596 0 0 0-.426-.173h-4.854a.596.596 0 0 0-.426.173.548.548 0 0 0-.18.409c0 .157.06.294.18.409l1.668 1.598-6.18 5.923a.281.281 0 0 0-.095.21c0 .078.031.148.094.208L7.67 9.983a.306.306 0 0 0 .436 0l6.18-5.923 1.67 1.599c.12.115.262.172.426.172a.596.596 0 0 0 .426-.172.547.547 0 0 0 .18-.409V.599a.548.548 0 0 0-.18-.409z"/>
</g>
</svg>
);
)
const ExternalIcon = styled.span`
margin-left: 1.2rem;
position: relative;
top: 1px;
zIndex: -1;
z-index: -1;
`
const baseInputStyles = css<IStyledLink>`
display: inline-flex;
align-items: center;
color: ${props => props.external ? props.theme.linkExternalColor : props.theme.linkInternalColor};
color: ${(props) =>
props.external
? props.theme.linkExternalColor
: props.theme.linkInternalColor};
${props => props.marginRight ? "margin-right: 3rem;" : ""}
${props => props.inheritColor ? "color: inherit;" : ""}
${props => props.underline ? "text-decoration: underline;" : ""}
${props => props.noUnderline ? ":hover { text-decoration: none };" : ""}
${(props) => (props.marginRight ? 'margin-right: 3rem;' : '')}
${(props) => (props.inheritColor ? 'color: inherit;' : '')}
${(props) => (props.underline ? 'text-decoration: underline;' : '')}
${(props) => (props.noUnderline ? ':hover { text-decoration: none };' : '')}
`
const StyledLink = styled.a<IStyledLink>`
......@@ -62,47 +64,67 @@ const StyledLink = styled.a<IStyledLink>`
// `
function Link(props: ILink) {
const {children, external, to, noExternalIcon, mail} = props
if (mail) {
return (
<StyledLink href={`mailto:${to}`} {...props}>
{children}
</StyledLink>
);
}
if (external) {
const urlRegex = /^((http|https):\/\/)/;
const href = urlRegex.test(to) ? to : `//${to}`;
return (
<StyledLink
href={href}
target="_blank"
rel="noopener noreferrer"
{...props}
>
{children}
{!noExternalIcon && (
<ExternalIcon>{externalLinkIcon}</ExternalIcon>
)}
</StyledLink>
);
}
// TODO Use StyledRouterLink for internal links when routes are set up
// return (
// <StyledRouterLink {...props}>
// {children}
// </StyledRouterLink>
// )
const { children, external, to, noExternalIcon, mail, underline, marginRight, inheritColor, noUnderline } = props
if (mail) {
return (
<StyledLink {...props}>
{children}
</StyledLink>
<StyledLink href={`mailto:${to}`}
external={external}
underline={underline}
marginRight={marginRight}
inheritColor={inheritColor}
noUnderline={noUnderline}>
{children}
</StyledLink>
)
}
if (external) {
const urlRegex = /^((http|https):\/\/)/
const href = urlRegex.test(to) ? to : `//${to}`
return (
<StyledLink
href={href}
target='_blank'
rel='noopener noreferrer'
external={external}
underline={underline}
marginRight={marginRight}
inheritColor={inheritColor}
noUnderline={noUnderline}
>
{children}
{!noExternalIcon && <ExternalIcon>{externalLinkIcon}</ExternalIcon>}
</StyledLink>
)
}
// TODO Use StyledRouterLink for internal links when routes are set up
// return (
// <StyledRouterLink {...props}>
// {children}
// </StyledRouterLink>
// )
return <StyledLink
external={external}
underline={underline}
marginRight={marginRight}
inheritColor={inheritColor}
noUnderline={noUnderline}>
{children}</StyledLink>
}
Link.defaultProps = {
external: false,
children: undefined,
marginRight: false,
noExternalIcon: false,
mail: false,
inheritColor: false,
underline: false,
noUnderline: false,
}
export default Link
\ No newline at end of file
export default Link
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