Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

638: Per region app policy #647

Merged
merged 32 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
01e54f2
638: add route for region, restrict route to admins, add region butto…
f1sh1918 Nov 22, 2022
bf88e8e
638: add basePolicy, add textarea
f1sh1918 Nov 23, 2022
bb91695
638: add data privacy policy checkbox, add policy dialog
f1sh1918 Nov 24, 2022
2a6a39d
638: add privacy base text
f1sh1918 Nov 28, 2022
f37a8cb
638: add dataPrivacyPolicy column, add get functions, implement data …
f1sh1918 Nov 28, 2022
faed460
638: add updateDataPolicy fkt
f1sh1918 Nov 28, 2022
51ebb9a
638: update db schema to make dataPrivacy nullabled, pass dataPrivacy…
f1sh1918 Nov 29, 2022
8a0c4d6
638: update format
f1sh1918 Nov 29, 2022
5796fb0
638: update format #2
f1sh1918 Nov 29, 2022
1553bd4
638: prettier
f1sh1918 Nov 29, 2022
2cd60f9
638: ktlin
f1sh1918 Nov 29, 2022
8f101df
638: replace anchor by button
f1sh1918 Nov 30, 2022
601dbf1
Update administration/src/components/regions/RegionOverview.tsx
f1sh1918 Dec 5, 2022
c3c8f6c
Update administration/src/components/regions/RegionOverview.tsx
f1sh1918 Dec 5, 2022
9869903
Update administration/src/components/regions/RegionOverview.tsx
f1sh1918 Dec 5, 2022
8db8ba7
Update backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/web…
f1sh1918 Dec 5, 2022
e5d1a06
Update administration/src/constants/dataPrivacyBase.ts
f1sh1918 Dec 5, 2022
36764fe
Merge remote-tracking branch 'origin/whitelabel' into 638-per-region-…
f1sh1918 Dec 5, 2022
a073f94
638: adjust basicDialog, reimplement privacy policy modal
f1sh1918 Dec 5, 2022
dd65287
638: use inline style for mui. restrict accessibility to Region Admins
f1sh1918 Dec 6, 2022
96bccb0
638: add auth check for privacyPolicyUpdate, add character counter fo…
f1sh1918 Dec 6, 2022
b54793d
638: fix formatting
f1sh1918 Dec 6, 2022
1c14046
Update administration/src/application/components/ApplyController.tsx
f1sh1918 Dec 12, 2022
59d7fae
Update administration/src/application/components/forms/StepSendForm.tsx
f1sh1918 Dec 12, 2022
fc67601
Update administration/src/components/regions/RegionOverview.tsx
f1sh1918 Dec 12, 2022
d8b9980
638: add privacy policy length check in backend, added todo for getDa…
f1sh1918 Dec 13, 2022
1434f14
Update administration/src/application/components/ApplyController.tsx
f1sh1918 Dec 14, 2022
50c9b5c
638: readded onBlur to FormGroup, adjusted RegionOverview style
f1sh1918 Dec 14, 2022
a1b5938
Merge remote-tracking branch 'origin/whitelabel' into 638-per-region-…
f1sh1918 Dec 14, 2022
39481cc
638: merge whitelabel branch and format frontend and backend
f1sh1918 Dec 14, 2022
652a564
Update administration/src/application/components/ApplyController.tsx
f1sh1918 Dec 14, 2022
51cce3a
638: fix typo
f1sh1918 Dec 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion administration/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import Navigation from './components/Navigation'
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom'
import GenerationController from './components/generation/GenerationController'
import styled from 'styled-components'
import RegionProvider from './RegionProvider'
Expand All @@ -12,13 +12,16 @@ import KeepAliveToken from './KeepAliveToken'
import ApplicationsController from './components/applications/ApplicationsController'
import { ProjectConfigProvider } from './project-configs/ProjectConfigContext'
import HomeController from './components/home/HomeController'
import RegionsController from './components/regions/RegionController'
import MetaTagsManager from './components/MetaTagsManager'
import { AppToasterProvider } from './components/AppToaster'
import UserSettingsController from './components/user-settings/UserSettingsController'
import ResetPasswordController from './components/auth/ResetPasswordController'
import ForgotPasswordController from './components/auth/ForgotPasswordController'
import ApplyController from './application/components/ApplyController'
import { createUploadLink } from 'apollo-upload-client'
import { Role } from './generated/graphql'
import DataPrivacyPolicy from './components/DataPrivacyPolicy'

if (!process.env.REACT_APP_API_BASE_URL) {
throw new Error('REACT_APP_API_BASE_URL is not set!')
Expand Down Expand Up @@ -47,6 +50,8 @@ const Main = styled.div`
justify-content: center;
`

const isRegionAdmin = (role: Role): boolean => role === Role.RegionAdmin

const App = () => (
<ProjectConfigProvider>
<MetaTagsManager />
Expand All @@ -58,6 +63,7 @@ const App = () => (
<BrowserRouter>
<Routes>
<Route path={'/forgot-password'} element={<ForgotPasswordController />} />
<Route path={'/data-privacy-policy'} element={<DataPrivacyPolicy />} />
<Route path={'/apply-for-eak'} element={<ApplyController />} />
<Route path={'/reset-password/:passwordResetKey'} element={<ResetPasswordController />} />
<Route
Expand All @@ -75,6 +81,16 @@ const App = () => (
path={'/applications'}
element={<ApplicationsController token={authData.token} />}
/>
<Route
path={'/region'}
element={
isRegionAdmin(authData.administrator.role) ? (
<RegionsController />
) : (
<Navigate to={'/'} />
)
}
/>
<Route path={'/eak-generation'} element={<GenerationController />} />
<Route path={'/user-settings'} element={<UserSettingsController />} />
<Route path={'*'} element={<HomeController />} />
Expand Down
19 changes: 19 additions & 0 deletions administration/src/ErrorHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Button, Card, H3 } from '@blueprintjs/core'
import React, { ReactElement } from 'react'

type ErrorHandlerProps = {
refetch: () => void
}

const ErrorHandler = ({ refetch }: ErrorHandlerProps): ReactElement => {
return (
<Card>
<H3>Ein Fehler ist aufgetreten.</H3>
<Button intent='primary' onClick={() => refetch()}>
Erneut versuchen
</Button>
</Card>
)
}

export default ErrorHandler
19 changes: 16 additions & 3 deletions administration/src/application/components/ApplyController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import '@fontsource/roboto/400.css'
import '@fontsource/roboto/500.css'
import '@fontsource/roboto/700.css'

import { useAddBlueEakApplicationMutation } from '../../generated/graphql'
import { useAddBlueEakApplicationMutation, useGetDataPolicyQuery } from '../../generated/graphql'
import { DialogActions } from '@mui/material'
import useVersionedLocallyStoredState from '../useVersionedLocallyStoredState'
import DiscardAllInputsButton from './DiscardAllInputsButton'
Expand All @@ -18,6 +18,7 @@ import ApplicationErrorBoundary from '../ApplicationErrorBoundary'
const lastCommitForApplicationForm = process.env.REACT_APP_APPLICATION_COMMIT as string

export const applicationStorageKey = 'applicationState'
const regionId = 1 // TODO: Add a mechanism to retrieve the regionId

const ApplyController = () => {
const [addBlueEakApplication, { loading }] = useAddBlueEakApplicationMutation()
Expand All @@ -26,6 +27,10 @@ const ApplyController = () => {
applicationStorageKey,
lastCommitForApplicationForm
)
const { loading: loadingPolicy, data: policyData } = useGetDataPolicyQuery({
f1sh1918 marked this conversation as resolved.
Show resolved Hide resolved
variables: { regionId: regionId },
onError: error => console.error(error),
})
const arrayBufferManagerInitialized = useInitializeGlobalArrayBuffersManager()
const getArrayBufferKeys = useMemo(
() => (status === 'loading' ? null : () => ApplicationForm.getArrayBufferKeys(state)),
Expand Down Expand Up @@ -63,8 +68,16 @@ const ApplyController = () => {
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'start', margin: '16px' }}>
<div style={{ maxWidth: '1000px', width: '100%' }}>
<h2 style={{ textAlign: 'center' }}>Blaue Ehrenamtskarte beantragen</h2>
<ApplicationForm.Component state={state} setState={setState} onSubmit={submit} loading={loading} />
<DialogActions>{loading ? null : <DiscardAllInputsButton discardAll={discardAll} />}</DialogActions>
<ApplicationForm.Component
state={state}
setState={setState}
onSubmit={submit}
loading={loading}
f1sh1918 marked this conversation as resolved.
Show resolved Hide resolved
privacyPolicy={policyData?.dataPolicy.dataPrivacyPolicy ?? ''}
/>
<DialogActions>
{loading || loadingPolicy ? null : <DiscardAllInputsButton discardAll={discardAll} />}
</DialogActions>
</div>
</div>
)
Expand Down
44 changes: 44 additions & 0 deletions administration/src/application/components/BasicDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import CloseIcon from '@mui/icons-material/Close'
import { Breakpoint, Dialog, DialogContent, DialogContentText, DialogTitle } from '@mui/material'
import styled from 'styled-components'

const StyledDialogTitle = styled(DialogTitle)`
display: flex;
justify-content: space-between;
`

const StyledCloseIcon = styled(CloseIcon)`
cursor: pointer;
`

const StyledDialogText = styled(DialogContentText)`
white-space: pre-line;
`

const BasicDialog = ({
open,
onUpdateOpen,
title,
content,
maxWidth,
}: {
open: boolean
onUpdateOpen: (open: boolean) => void
title: string
content: string
maxWidth?: Breakpoint | false
}) => {
return (
<Dialog open={open} onBackdropClick={() => onUpdateOpen(false)} maxWidth={maxWidth}>
<StyledDialogTitle>
{title}
<StyledCloseIcon onClick={() => onUpdateOpen(false)} />
</StyledDialogTitle>
<DialogContent>
<StyledDialogText>{content}</StyledDialogText>
</DialogContent>
</Dialog>
)
}

export default BasicDialog
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type ApplicationFormState = {
}
type ValidatedInput = [RegionId, BlueCardApplicationInput]
type Options = {}
type AdditionalProps = { onSubmit: () => void; loading: boolean }
type AdditionalProps = { onSubmit: () => void; loading: boolean; privacyPolicy: string }
const ApplicationForm: Form<ApplicationFormState, Options, ValidatedInput, AdditionalProps> = {
initialState: {
activeStep: 0,
Expand Down Expand Up @@ -56,7 +56,7 @@ const ApplicationForm: Form<ApplicationFormState, Options, ValidatedInput, Addit
],
}
},
Component: ({ state, setState, onSubmit, loading }) => {
Component: ({ state, setState, onSubmit, loading, privacyPolicy }) => {
const personalDataStep = useFormAsStep(
'Persönliche Angaben',
PersonalDataForm,
Expand All @@ -76,7 +76,7 @@ const ApplicationForm: Form<ApplicationFormState, Options, ValidatedInput, Addit
{ cardType: state.stepCardType.cardType },
{}
)
const sendStep = useFormAsStep('Antrag Senden', StepSendForm, state, setState, 'stepSend', {}, {})
const sendStep = useFormAsStep('Antrag Senden', StepSendForm, state, setState, 'stepSend', {}, { privacyPolicy })
return (
<SteppedSubForms
activeStep={state.activeStep}
Expand Down
62 changes: 44 additions & 18 deletions administration/src/application/components/forms/StepSendForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useUpdateStateCallback } from '../../useUpdateStateCallback'
import { Form } from '../../FormType'
import CheckboxForm, { CheckboxFormState } from '../primitive-inputs/CheckboxForm'
import { Button } from '@mui/material'
import BasicDialog from '../BasicDialog'
import { useState } from 'react'
import { dataPrivacyBaseHeadline, dataPrivacyBaseText } from '../../../constants/dataPrivacyBase'

const acceptedDatePrivacyOptions: { required: boolean; notCheckedErrorMessage: string } = {
required: true,
Expand All @@ -20,7 +24,7 @@ type ValidatedInput = {
givenInformationIsCorrectAndComplete: boolean
}
type Options = {}
type AdditionalProps = {}
type AdditionalProps = { privacyPolicy: string }
const StepSendForm: Form<StepSendFormState, Options, ValidatedInput, AdditionalProps> = {
initialState: {
acceptedDataPrivacy: CheckboxForm.initialState,
Expand All @@ -46,23 +50,45 @@ const StepSendForm: Form<StepSendFormState, Options, ValidatedInput, AdditionalP
},
}
},
Component: ({ state, setState }) => (
<>
<CheckboxForm.Component
label='Ich erkläre mich damit einverstanden, dass meine Daten zum Zwecke der Antragsverarbeitung
gespeichert werden.'
state={state.acceptedDataPrivacy}
setState={useUpdateStateCallback(setState, 'acceptedDataPrivacy')}
options={acceptedDatePrivacyOptions}
/>
<CheckboxForm.Component
label='Ich versichere, dass alle angegebenen Informationen korrekt und vollständig sind.'
state={state.givenInformationIsCorrectAndComplete}
setState={useUpdateStateCallback(setState, 'givenInformationIsCorrectAndComplete')}
options={givenInformationIsCorrectAndCompleteOptions}
/>
</>
),
Component: ({ state, setState, privacyPolicy }) => {
const [openPrivacyPolicy, setOpenPrivacyPolicy] = useState<boolean>(false)
const PrivacyLabel = (
<div style={{ alignSelf: 'center' }}>
Ich erkläre mich damit einverstanden, dass meine Daten zum Zwecke der Antragsverarbeitung gespeichert werden und
akzeptiere die{' '}
<Button
variant='text'
style={{ textTransform: 'capitalize', padding: 0, verticalAlign: 'unset' }}
onClick={() => setOpenPrivacyPolicy(true)}>
Datenschutzerklärung
</Button>
f1sh1918 marked this conversation as resolved.
Show resolved Hide resolved
</div>
)
return (
<>
<CheckboxForm.Component
state={state.acceptedDataPrivacy}
setState={useUpdateStateCallback(setState, 'acceptedDataPrivacy')}
options={acceptedDatePrivacyOptions}
label={PrivacyLabel}
/>

<CheckboxForm.Component
label='Ich versichere, dass alle angegebenen Informationen korrekt und vollständig sind.'
state={state.givenInformationIsCorrectAndComplete}
setState={useUpdateStateCallback(setState, 'givenInformationIsCorrectAndComplete')}
options={givenInformationIsCorrectAndCompleteOptions}
/>
<BasicDialog
open={openPrivacyPolicy}
maxWidth='lg'
onUpdateOpen={() => setOpenPrivacyPolicy(false)}
f1sh1918 marked this conversation as resolved.
Show resolved Hide resolved
title={dataPrivacyBaseHeadline}
content={`${dataPrivacyBaseText}\n${privacyPolicy}`}
/>
</>
)
},
}

export default StepSendForm
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Checkbox, FormControl, FormControlLabel, FormGroup, FormHelperText } from '@mui/material'
import { useContext, useState } from 'react'
import { useContext, useState, ReactElement } from 'react'
import { Form } from '../../FormType'
import { FormContext } from '../SteppedSubForms'

export type CheckboxFormState = { checked: boolean }
type ValidatedInput = boolean
type Options = { required: true; notCheckedErrorMessage: string } | { required: false }
type AdditionalProps = { label: string }
type AdditionalProps = { label: string | ReactElement }
const CheckboxForm: Form<CheckboxFormState, Options, ValidatedInput, AdditionalProps> = {
initialState: { checked: false },
getArrayBufferKeys: () => [],
Expand All @@ -22,7 +22,7 @@ const CheckboxForm: Form<CheckboxFormState, Options, ValidatedInput, AdditionalP
const isInvalid = validationResult.type === 'error'

return (
<FormGroup onBlur={() => setTouched(true)}>
f1sh1918 marked this conversation as resolved.
Show resolved Hide resolved
<FormGroup>
<FormControl required={options.required} error={touched && isInvalid} disabled={disableAllInputs}>
<FormControlLabel
style={{ margin: '8px 0' }}
Expand Down
24 changes: 24 additions & 0 deletions administration/src/components/DataPrivacyPolicy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { H1 } from '@blueprintjs/core'
import React, { ReactElement } from 'react'
import styled from 'styled-components'
import { dataPrivacyBaseHeadline, dataPrivacyBaseText } from '../constants/dataPrivacyBase'

const Content = styled.div`
white-space: pre-line;
margin-top: 2rem;
`
const Container = styled.div`
max-width: 60%;
display: flex;
flex-direction: column;
align-self: center;
`

const DataPrivacyPolicy = (): ReactElement => (
<Container>
<H1>{dataPrivacyBaseHeadline}</H1>
<Content>{dataPrivacyBaseText}</Content>
</Container>
)

export default DataPrivacyPolicy
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import React, { useContext } from 'react'
import { Button, Card, H3, Spinner } from '@blueprintjs/core'
import { Spinner } from '@blueprintjs/core'
import { RegionContext } from '../../RegionProvider'
import ApplicationsOverview from './ApplicationsOverview'
import { Region, useGetApplicationsQuery } from '../../generated/graphql'
import ErrorHandler from '../../ErrorHandler'

const ApplicationsController = (props: { region: Region; token: string }) => {
const { loading, error, data, refetch } = useGetApplicationsQuery({
variables: { regionId: props.region.id },
onError: error => console.error(error),
})
if (loading) return <Spinner />
else if (error || !data)
return (
<Card>
<H3>Ein Fehler ist aufgetreten.</H3>
<Button intent='primary' onClick={() => refetch()}>
Erneut versuchen
</Button>
</Card>
)
else if (error || !data) return <ErrorHandler refetch={refetch} />
else return <ApplicationsOverview applications={data.applications} token={props.token} />
}

Expand Down
Loading