Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • andretol/greg
1 result
Show changes
Commits on Source (32)
Showing
with 5671 additions and 4531 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.
......@@ -17,6 +17,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.15.3",
"react-i18next": "^11.11.4",
"react-router-dom": "^5.3.0",
"react-scripts": "4.0.3",
......@@ -25,11 +26,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": {
......@@ -52,6 +58,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',
}
......@@ -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"
}
{
"footerInfo": "This is a footer namespace value"
"footerInfo": "This is a footer namespace value",
"contactSectionHeader": "Need help?",
"contactHelp": "Contact",
"contactHelpMail": "Contact",
"responsibleOrganizationHeader": "Maintained by"
}
......@@ -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"
}
{
"footerInfo": "Verdi i namespace footer"
"footerInfo": "Verdi i namespace footer",
"contactSectionHeader": "Trenger du hjelp?",
"contactHelp": "Kontakt",
"contactHelpMail": "Kontaktveileder",
"responsibleOrganizationHeader": "Ansvarlig for tjenesten"
}
......@@ -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"
}
{
"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