update linting

This commit is contained in:
Michael Schramm 2020-06-09 16:27:56 +02:00
parent a983cb218a
commit acb26096d6
57 changed files with 372 additions and 220 deletions

View File

@ -3,6 +3,8 @@ module.exports = {
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
ecmaFeatures: { jsx: true }, ecmaFeatures: { jsx: true },
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
}, },
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
@ -12,11 +14,13 @@ module.exports = {
'plugin:jsx-a11y/recommended', 'plugin:jsx-a11y/recommended',
'prettier/@typescript-eslint', 'prettier/@typescript-eslint',
'plugin:prettier/recommended', 'plugin:prettier/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
], ],
rules: { rules: {
'prettier/prettier': ['error', {}, { usePrettierrc: true }], 'prettier/prettier': ['error', {}, { usePrettierrc: true }],
'react/prop-types': 'off', 'react/prop-types': 'off',
'@typescript-eslint/no-empty-interface': 'off', '@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-var-requires': 'off',
}, },
settings: { settings: {
react: { react: {

View File

@ -1,7 +1,7 @@
# OhMyForm UI # OhMyForm UI
![Project Status](https://badgen.net/github/checks/ohmyform/ui) ![Project Status](https://badgen.net/github/checks/ohmyform/ui)
![Travis Status](https://badgen.net/travis/ohmyform/ui) [![Build Status](https://travis-ci.org/ohmyform/ui.svg?branch=master)](https://travis-ci.org/ohmyform/ui)
![Latest Release](https://badgen.net/github/tag/ohmyform/ui) ![Latest Release](https://badgen.net/github/tag/ohmyform/ui)
![Docker Pulls](https://badgen.net/docker/pulls/ohmyform/ui) ![Docker Pulls](https://badgen.net/docker/pulls/ohmyform/ui)
[![Lokalise](https://badgen.net/badge/Lokalise/EN/green?icon=libraries)](https://app.lokalise.com/public/379418475ede5d5c6937b0.31012044/) [![Lokalise](https://badgen.net/badge/Lokalise/EN/green?icon=libraries)](https://app.lokalise.com/public/379418475ede5d5c6937b0.31012044/)

View File

@ -1,3 +1,4 @@
/* eslint-disable */
const omitDeepArrayWalk = (arr, key) => { const omitDeepArrayWalk = (arr, key) => {
return arr.map((val) => { return arr.map((val) => {
if (Array.isArray(val)) return omitDeepArrayWalk(val, key) if (Array.isArray(val)) return omitDeepArrayWalk(val, key)
@ -6,16 +7,11 @@ const omitDeepArrayWalk = (arr, key) => {
}) })
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const omitDeep = (obj: any, key: string | number): any => { const omitDeep = (obj: any, key: string | number): any => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const keys: Array<any> = Object.keys(obj) const keys: Array<any> = Object.keys(obj)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newObj: any = {} const newObj: any = {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
keys.forEach((i: any) => { keys.forEach((i: any) => {
if (i !== key) { if (i !== key) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const val: any = obj[i] const val: any = obj[i]
if (val instanceof Date) newObj[i] = val if (val instanceof Date) newObj[i] = val
else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key) else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key)

View File

@ -21,10 +21,10 @@ export const FieldCard: React.FC<Props> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { form, field, fields, onChangeFields, remove, index } = props const { form, field, fields, onChangeFields, remove, index } = props
const type = form.getFieldValue(['form', 'fields', field.name as string, 'type']) const type = form.getFieldValue(['form', 'fields', field.name as string, 'type']) as string
const TypeComponent = adminTypes[type] || TextType const TypeComponent = adminTypes[type] || TextType
const [nextTitle, setNextTitle] = useState( const [nextTitle, setNextTitle] = useState<string>(
form.getFieldValue(['form', 'fields', field.name as string, 'title']) form.getFieldValue(['form', 'fields', field.name as string, 'title'])
) )

View File

@ -2,6 +2,7 @@ import { PlusOutlined } from '@ant-design/icons/lib'
import { Button, Form, Select, Space, Tabs } from 'antd' import { Button, Form, Select, Space, Tabs } from 'antd'
import { FormInstance } from 'antd/lib/form' import { FormInstance } from 'antd/lib/form'
import { TabPaneProps } from 'antd/lib/tabs' import { TabPaneProps } from 'antd/lib/tabs'
import { FieldData } from 'rc-field-form/lib/interface'
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment' import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
@ -19,7 +20,7 @@ export const FieldsTab: React.FC<Props> = (props) => {
const [nextType, setNextType] = useState('textfield') const [nextType, setNextType] = useState('textfield')
const renderType = useCallback( const renderType = useCallback(
(field, index, remove) => { (field: FieldData, index: number, remove: (index: number) => void) => {
return ( return (
<FieldCard <FieldCard
form={props.form} form={props.form}
@ -35,7 +36,7 @@ export const FieldsTab: React.FC<Props> = (props) => {
) )
const addField = useCallback( const addField = useCallback(
(add, index) => { (add: (defaults: unknown) => void, index: number) => {
return ( return (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
<Space <Space

View File

@ -16,7 +16,7 @@ export const RespondentNotificationsTab: React.FC<Props> = (props) => {
const [enabled, setEnabled] = useState<boolean>() const [enabled, setEnabled] = useState<boolean>()
useEffect(() => { useEffect(() => {
const next = props.form.getFieldValue(['form', 'respondentNotifications', 'enabled']) const next = props.form.getFieldValue(['form', 'respondentNotifications', 'enabled']) as boolean
if (next !== enabled) { if (next !== enabled) {
setEnabled(next) setEnabled(next)
@ -24,14 +24,18 @@ export const RespondentNotificationsTab: React.FC<Props> = (props) => {
}, [props.form.getFieldValue(['form', 'respondentNotifications', 'enabled'])]) }, [props.form.getFieldValue(['form', 'respondentNotifications', 'enabled'])])
useEffect(() => { useEffect(() => {
props.form.validateFields([ props.form
['form', 'respondentNotifications', 'subject'], .validateFields([
['form', 'respondentNotifications', 'htmlTemplate'], ['form', 'respondentNotifications', 'subject'],
['form', 'respondentNotifications', 'toField'], ['form', 'respondentNotifications', 'htmlTemplate'],
]) ['form', 'respondentNotifications', 'toField'],
])
.catch((e: Error) => console.error('failed to validate fields', e))
}, [enabled]) }, [enabled])
const groups = {} const groups: {
[key: string]: AdminFormFieldFragment[]
} = {}
props.fields.forEach((field) => { props.fields.forEach((field) => {
if (!groups[field.type]) { if (!groups[field.type]) {

View File

@ -16,7 +16,7 @@ export const SelfNotificationsTab: React.FC<Props> = (props) => {
const [enabled, setEnabled] = useState<boolean>() const [enabled, setEnabled] = useState<boolean>()
useEffect(() => { useEffect(() => {
const next = props.form.getFieldValue(['form', 'selfNotifications', 'enabled']) const next = props.form.getFieldValue(['form', 'selfNotifications', 'enabled']) as boolean
if (next !== enabled) { if (next !== enabled) {
setEnabled(next) setEnabled(next)
@ -24,13 +24,17 @@ export const SelfNotificationsTab: React.FC<Props> = (props) => {
}, [props.form.getFieldValue(['form', 'selfNotifications', 'enabled'])]) }, [props.form.getFieldValue(['form', 'selfNotifications', 'enabled'])])
useEffect(() => { useEffect(() => {
props.form.validateFields([ props.form
['form', 'selfNotifications', 'subject'], .validateFields([
['form', 'selfNotifications', 'htmlTemplate'], ['form', 'selfNotifications', 'subject'],
]) ['form', 'selfNotifications', 'htmlTemplate'],
])
.catch((e: Error) => console.error('failed to validate', e))
}, [enabled]) }, [enabled])
const groups = {} const groups: {
[key: string]: AdminFormFieldFragment[]
} = {}
props.fields.forEach((field) => { props.fields.forEach((field) => {
if (!groups[field.type]) { if (!groups[field.type]) {
groups[field.type] = [] groups[field.type] = []

View File

@ -19,7 +19,7 @@ export const SubmissionValues: React.FC<Props> = (props) => {
const columns: ColumnsType<AdminPagerSubmissionEntryFieldQueryData> = [ const columns: ColumnsType<AdminPagerSubmissionEntryFieldQueryData> = [
{ {
title: t('submission:field'), title: t('submission:field'),
render: (row: AdminPagerSubmissionEntryFieldQueryData) => { render(row: AdminPagerSubmissionEntryFieldQueryData) {
if (row.field) { if (row.field) {
return `${row.field.title}${row.field.required ? '*' : ''}` return `${row.field.title}${row.field.required ? '*' : ''}`
} }
@ -29,9 +29,9 @@ export const SubmissionValues: React.FC<Props> = (props) => {
}, },
{ {
title: t('submission:value'), title: t('submission:value'),
render: (row) => { render(row: AdminPagerSubmissionEntryFieldQueryData) {
try { try {
const data = JSON.parse(row.value) const data = JSON.parse(row.value) as { value: string }
return data.value return data.value
} catch (e) { } catch (e) {

View File

@ -1,5 +1,5 @@
import { DatePicker, Form } from 'antd' import { DatePicker, Form } from 'antd'
import moment from 'moment' import moment, { Moment } from 'moment'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { AdminFieldTypeProps } from './type.props' import { AdminFieldTypeProps } from './type.props'
@ -11,29 +11,29 @@ export const DateType: React.FC<AdminFieldTypeProps> = ({ field }) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:date.default')} label={t('type:date.default')}
name={[field.name, 'value']} name={[field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
getValueFromEvent={(e) => (e ? e.format('YYYY-MM-DD') : undefined)} getValueFromEvent={(e: Moment) => (e ? e.format('YYYY-MM-DD') : undefined)}
getValueProps={(e) => ({ value: e ? moment(e) : undefined })} getValueProps={(e: string) => ({ value: e ? moment(e) : undefined })}
> >
<DatePicker format={'YYYY-MM-DD'} /> <DatePicker format={'YYYY-MM-DD'} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t('type:date.min')} label={t('type:date.min')}
name={[field.name, 'optionKeys', 'min']} name={[field.name as string, 'optionKeys', 'min']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
getValueFromEvent={(e) => e.format('YYYY-MM-DD')} getValueFromEvent={(e: Moment) => e.format('YYYY-MM-DD')}
getValueProps={(e) => ({ value: e ? moment(e) : undefined })} getValueProps={(e: string) => ({ value: e ? moment(e) : undefined })}
> >
<DatePicker /> <DatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t('type:date.max')} label={t('type:date.max')}
name={[field.name, 'optionKeys', 'max']} name={[field.name as string, 'optionKeys', 'max']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
getValueFromEvent={(e) => e.format('YYYY-MM-DD')} getValueFromEvent={(e: Moment) => e.format('YYYY-MM-DD')}
getValueProps={(e) => ({ value: e ? moment(e) : undefined })} getValueProps={(e: string) => ({ value: e ? moment(e) : undefined })}
> >
<DatePicker /> <DatePicker />
</Form.Item> </Form.Item>

View File

@ -10,13 +10,13 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:dropdown.default')} label={t('type:dropdown.default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.List name={[props.field.name, 'options']}> <Form.List name={[props.field.name as string, 'options']}>
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
return ( return (
<div> <div>

View File

@ -10,7 +10,7 @@ export const EmailType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:email.default')} label={t('type:email.default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
rules={[{ type: 'email', message: t('validation:emailRequired') }]} rules={[{ type: 'email', message: t('validation:emailRequired') }]}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >

View File

@ -10,7 +10,7 @@ export const HiddenType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:hidden.default')} label={t('type:hidden.default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input /> <Input />

View File

@ -10,7 +10,7 @@ export const LinkType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:link.default')} label={t('type:link.default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
rules={[{ type: 'url', message: t('validation:invalidUrl') }]} rules={[{ type: 'url', message: t('validation:invalidUrl') }]}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >

View File

@ -10,7 +10,7 @@ export const NumberType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:number:default')} label={t('type:number:default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<InputNumber /> <InputNumber />

View File

@ -10,13 +10,13 @@ export const RadioType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:radio:default')} label={t('type:radio:default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.List name={[props.field.name, 'options']}> <Form.List name={[props.field.name as string, 'options']}>
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
return ( return (
<div> <div>

View File

@ -11,7 +11,7 @@ export const RatingType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:rating:default')} label={t('type:rating:default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
extra={t('type:rating.clearNote')} extra={t('type:rating.clearNote')}
> >

View File

@ -9,7 +9,7 @@ export const TextType: React.FC<AdminFieldTypeProps> = (props) => {
return ( return (
<Form.Item <Form.Item
label={t('type:textfield:default')} label={t('type:textfield:default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input /> <Input />

View File

@ -10,7 +10,7 @@ export const TextareaType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:textarea:default')} label={t('type:textarea:default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input.TextArea autoSize /> <Input.TextArea autoSize />

View File

@ -1,6 +1,7 @@
import { FormInstance } from 'antd/lib/form' import { FormInstance } from 'antd/lib/form'
import { FieldData } from 'rc-field-form/lib/interface'
export interface AdminFieldTypeProps { export interface AdminFieldTypeProps {
form: FormInstance form: FormInstance
field: { name: string } field: FieldData
} }

View File

@ -11,7 +11,7 @@ export const YesNoType: React.FC<AdminFieldTypeProps> = (props) => {
<div> <div>
<Form.Item <Form.Item
label={t('type:yes_no:default')} label={t('type:yes_no:default')}
name={[props.field.name, 'value']} name={[props.field.name as string, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input /> <Input />

View File

@ -30,8 +30,8 @@ export const Field: React.FC<Props> = ({ field, save, design, next, prev, ...pro
next() next()
} }
const error = () => { const error = async () => {
message.error('Check inputs!') await message.error('Check inputs!')
} }
return ( return (

View File

@ -25,8 +25,8 @@ export const DateType: React.FC<FieldTypeProps> = ({ field, design }) => {
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[{ required: field.required, message: 'Please provide Information' }]} rules={[{ required: field.required, message: 'Please provide Information' }]}
getValueFromEvent={(e) => e.format('YYYY-MM-DD')} getValueFromEvent={(e: Moment) => e.format('YYYY-MM-DD')}
getValueProps={(e) => ({ value: e ? moment(e) : undefined })} getValueProps={(e: string) => ({ value: e ? moment(e) : undefined })}
initialValue={field.value ? moment(field.value) : undefined} initialValue={field.value ? moment(field.value) : undefined}
> >
<StyledDateInput <StyledDateInput

View File

@ -18,7 +18,7 @@ export const InputColor: React.FC<Props> = (props) => {
triangle={'hide'} triangle={'hide'}
width={'100%'} width={'100%'}
color={props.value} color={props.value}
onChange={(e) => props.onChange(e.hex)} onChange={(e: { hex: string }) => props.onChange(e.hex)}
styles={{ styles={{
default: { default: {
card: { card: {

View File

@ -105,9 +105,9 @@ const Structure: FunctionComponent<Props> = (props) => {
return ( return (
<Menu.Item <Menu.Item
onClick={(): void => { onClick={async () => {
if (element.href) { if (element.href) {
router.push(element.href) await router.push(element.href)
} }
}} }}
key={element.key} key={element.key}
@ -120,8 +120,8 @@ const Structure: FunctionComponent<Props> = (props) => {
) )
} }
const signOut = async (): Promise<void> => { const signOut = (): void => {
await clearAuth() clearAuth()
router.reload() router.reload()
} }
@ -147,7 +147,7 @@ const Structure: FunctionComponent<Props> = (props) => {
})} })}
<img <img
src={require('assets/images/logo_white_small.png')} src={require('assets/images/logo_white_small.png') as string}
height={30} height={30}
style={{ marginRight: 16 }} style={{ marginRight: 16 }}
alt={'OhMyForm'} alt={'OhMyForm'}

View File

@ -10,15 +10,16 @@ interface Props extends ButtonProps {
color: string color: string
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Styled = styled(Button)` const Styled = styled(Button)`
background: ${(props) => props.background}; background: ${(props: Props) => props.background};
color: ${(props) => props.color}; color: ${(props: Props) => props.color};
border-color: ${(props) => darken(props.background, 10)}; border-color: ${(props: Props) => darken(props.background, 10)};
:hover { :hover {
color: ${(props) => props.highlight}; color: ${(props: Props) => props.highlight};
background-color: ${(props) => lighten(props.background, 10)}; background-color: ${(props: Props) => lighten(props.background, 10)};
border-color: ${(props) => darken(props.highlight, 10)}; border-color: ${(props: Props) => darken(props.highlight, 10)};
} }
` `

View File

@ -1,3 +1,4 @@
/* eslint-disable */
/** /**
* @link https://css-tricks.com/snippets/javascript/lighten-darken-color/ * @link https://css-tricks.com/snippets/javascript/lighten-darken-color/
* *

View File

@ -8,9 +8,10 @@ import { transparentize } from './color.change'
type Props = { design: FormDesignFragment } & PickerProps<Moment> type Props = { design: FormDesignFragment } & PickerProps<Moment>
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(DatePicker)` const Field = styled(DatePicker)`
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
background: none !important; background: none !important;
border-right: none; border-right: none;
border-top: none; border-top: none;
@ -20,7 +21,7 @@ const Field = styled(DatePicker)`
:hover, :hover,
:active { :active {
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
} }
&.ant-picker { &.ant-picker {
@ -32,15 +33,15 @@ const Field = styled(DatePicker)`
} }
input { input {
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${(props) => transparentize(props.design.colors.answerColor, 60)}; color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
} }
` `

View File

@ -8,7 +8,7 @@ interface Props {
} }
const Header = styled.h1` const Header = styled.h1`
color: ${(props) => color: ${(props: Props) =>
props.type === 'question' props.type === 'question'
? props.design.colors.questionColor ? props.design.colors.questionColor
: props.design.colors.answerColor}; : props.design.colors.answerColor};

View File

@ -7,7 +7,7 @@ interface Props {
design: FormDesignFragment design: FormDesignFragment
} }
const Header = styled.h2` const Header = styled.h2`
color: ${(props) => color: ${(props: Props) =>
props.type === 'question' props.type === 'question'
? props.design.colors.questionColor ? props.design.colors.questionColor
: props.design.colors.answerColor}; : props.design.colors.answerColor};

View File

@ -9,9 +9,10 @@ interface Props extends InputProps {
design: FormDesignFragment design: FormDesignFragment
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(Input)` const Field = styled(Input)`
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
background: none !important; background: none !important;
border-right: none; border-right: none;
border-top: none; border-top: none;
@ -19,12 +20,12 @@ const Field = styled(Input)`
border-radius: 0; border-radius: 0;
:focus { :focus {
outline: ${(props) => props.design.colors.answerColor} auto 5px; outline: ${(props: Props) => props.design.colors.answerColor} auto 5px;
} }
:hover, :hover,
:active { :active {
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
} }
&.ant-input-affix-wrapper { &.ant-input-affix-wrapper {
@ -33,15 +34,15 @@ const Field = styled(Input)`
input { input {
background: none !important; background: none !important;
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${(props) => transparentize(props.design.colors.answerColor, 60)}; color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
} }
` `

View File

@ -9,9 +9,10 @@ interface Props extends InputNumberProps {
design: FormDesignFragment design: FormDesignFragment
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(InputNumber)` const Field = styled(InputNumber)`
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
background: none !important; background: none !important;
border-right: none; border-right: none;
border-top: none; border-top: none;
@ -20,12 +21,12 @@ const Field = styled(InputNumber)`
width: 100%; width: 100%;
:focus { :focus {
outline: ${(props) => props.design.colors.answerColor} auto 5px; outline: ${(props: Props) => props.design.colors.answerColor} auto 5px;
} }
:hover, :hover,
:active { :active {
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
} }
&.ant-input-number { &.ant-input-number {
@ -34,15 +35,15 @@ const Field = styled(InputNumber)`
input { input {
background: none !important; background: none !important;
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${(props) => transparentize(props.design.colors.answerColor, 60)}; color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
} }
` `

View File

@ -7,8 +7,9 @@ interface Props {
design: FormDesignFragment design: FormDesignFragment
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
const Paragraph = styled.p` const Paragraph = styled.p`
color: ${(props) => color: ${(props: Props) =>
props.type === 'question' props.type === 'question'
? props.design.colors.questionColor ? props.design.colors.questionColor
: props.design.colors.answerColor}; : props.design.colors.answerColor};

View File

@ -8,27 +8,28 @@ interface Props extends RadioProps {
design: FormDesignFragment design: FormDesignFragment
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(Radio)` const Field = styled(Radio)`
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
background: none; background: none;
.ant-radio { .ant-radio {
.ant-radio-inner { .ant-radio-inner {
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
&::after { &::after {
background: ${(props) => props.design.colors.answerColor}; background: ${(props: Props) => props.design.colors.answerColor};
} }
} }
&::after { &::after {
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
} }
} }
.anticon { .anticon {
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
} }
` `

View File

@ -9,10 +9,11 @@ interface Props extends SelectProps<string> {
design: FormDesignFragment design: FormDesignFragment
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(Select)` const Field = styled(Select)`
.ant-select-selector { .ant-select-selector {
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props) => props.design.colors.answerColor} !important; border-color: ${(props: Props) => props.design.colors.answerColor} !important;
background: none !important; background: none !important;
border-right: none !important; border-right: none !important;
border-top: none !important; border-top: none !important;
@ -22,25 +23,25 @@ const Field = styled(Select)`
} }
:focus { :focus {
outline: ${(props) => props.design.colors.answerColor} auto 5px; outline: ${(props: Props) => props.design.colors.answerColor} auto 5px;
} }
:hover, :hover,
:active { :active {
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
} }
input { input {
background: none !important; background: none !important;
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${(props) => transparentize(props.design.colors.answerColor, 60)}; color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
} }
` `

View File

@ -9,9 +9,10 @@ interface Props extends TextAreaProps {
design: FormDesignFragment design: FormDesignFragment
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(Input.TextArea)` const Field = styled(Input.TextArea)`
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
background: none !important; background: none !important;
border-right: none; border-right: none;
border-top: none; border-top: none;
@ -21,25 +22,25 @@ const Field = styled(Input.TextArea)`
:focus { :focus {
outline: none; outline: none;
box-shadow: none; box-shadow: none;
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
} }
:hover, :hover,
:active { :active {
border-color: ${(props) => props.design.colors.answerColor}; border-color: ${(props: Props) => props.design.colors.answerColor};
} }
input { input {
background: none !important; background: none !important;
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${(props) => transparentize(props.design.colors.answerColor, 60)}; color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${(props) => props.design.colors.answerColor}; color: ${(props: Props) => props.design.colors.answerColor};
} }
` `

View File

@ -13,7 +13,7 @@ import {
interface Submission { interface Submission {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
setField: (fieldId: string, data: any) => Promise<void> setField: (fieldId: string, data: unknown) => Promise<void>
finish: () => void finish: () => void
} }
@ -28,27 +28,28 @@ export const useSubmission = (formId: string): Submission => {
) )
useEffect(() => { useEffect(() => {
;(async () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const token = [...Array(40)].map(() => Math.random().toString(36)[2]).join('') const token = [...Array(40)].map(() => Math.random().toString(36)[2]).join('')
const { data } = await start({ start({
variables: { variables: {
form: formId, form: formId,
submission: { submission: {
token, token,
device: { device: {
name: /Mobi/i.test(window.navigator.userAgent) ? 'mobile' : 'desktop', name: /Mobi/i.test(window.navigator.userAgent) ? 'mobile' : 'desktop',
type: window.navigator.userAgent, type: window.navigator.userAgent,
},
}, },
}, },
},
})
.then(({ data }) => {
setSubmission({
id: data.submission.id,
token,
})
}) })
.catch((e: Error) => console.error('failed to start submission', e))
setSubmission({
id: data.submission.id,
token,
})
})()
}, [formId]) }, [formId])
const setField = useCallback( const setField = useCallback(

View File

@ -55,7 +55,7 @@ export const BaseDataTab: React.FC<TabPaneProps> = (props) => {
return ['user'] return ['user']
} }
}} }}
getValueProps={(v) => { getValueProps={(v: string[]) => {
let role = 'user' let role = 'user'
if (v && v.includes('superuser')) { if (v && v.includes('superuser')) {

View File

@ -6,7 +6,7 @@ interface Props {
} }
export const UserRole: React.FC<Props> = (props) => { export const UserRole: React.FC<Props> = (props) => {
let color let color: string
let level = 'unknown' let level = 'unknown'
const css: CSSProperties = {} const css: CSSProperties = {}

View File

@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
import { ME_QUERY, MeQueryData } from '../graphql/query/me.query' import { ME_QUERY, MeQueryData } from '../graphql/query/me.query'
import { LoadingPage } from './loading.page' import { LoadingPage } from './loading.page'
export const clearAuth = async (): Promise<void> => { export const clearAuth = (): void => {
localStorage.removeItem('access') localStorage.removeItem('access')
localStorage.removeItem('refresh') localStorage.removeItem('refresh')
@ -18,7 +18,7 @@ export const setAuth = (access: string, refresh: string): void => {
localStorage.setItem('refresh', refresh) localStorage.setItem('refresh', refresh)
} }
export const authConfig = async (config: AxiosRequestConfig = {}): Promise<AxiosRequestConfig> => { export const authConfig = (config: AxiosRequestConfig = {}): AxiosRequestConfig => {
if (!config.headers) { if (!config.headers) {
config.headers = {} config.headers = {}
} }
@ -28,6 +28,7 @@ export const authConfig = async (config: AxiosRequestConfig = {}): Promise<Axios
// TODO check for validity / use refresh token // TODO check for validity / use refresh token
if (token) { if (token) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
config.headers.Authorization = `Bearer ${token}` config.headers.Authorization = `Bearer ${token}`
} }
} catch (e) { } catch (e) {
@ -60,7 +61,7 @@ export const withAuth = (Component: any, roles: string[] = []): React.FC => {
const path = router.asPath || router.pathname const path = router.asPath || router.pathname
localStorage.setItem('redirect', path) localStorage.setItem('redirect', path)
router.push('/login') router.push('/login').catch((e: Error) => console.error('failed to redirect to login', e))
}, [error]) }, [error])
useEffect(() => { useEffect(() => {
@ -74,7 +75,7 @@ export const withAuth = (Component: any, roles: string[] = []): React.FC => {
setAccess(next) setAccess(next)
if (!next) { if (!next) {
router.push('/') router.push('/').catch((e: Error) => console.error('failed to redirect to /', e))
} }
}, [data]) }, [data])

View File

@ -1,5 +1,17 @@
import { gql } from 'apollo-boost' import { gql } from 'apollo-boost'
export interface LoginMutationData {
tokens: {
access: string
refresh: string
}
}
export interface LoginMutationVariables {
username: string
password: string
}
export const LOGIN_MUTATION = gql` export const LOGIN_MUTATION = gql`
mutation login($username: String!, $password: String!) { mutation login($username: String!, $password: String!) {
tokens: authLogin(username: $username, password: $password) { tokens: authLogin(username: $username, password: $password) {

View File

@ -1,5 +1,22 @@
import { gql } from 'apollo-boost' import { gql } from 'apollo-boost'
export interface RegisterMutationData {
tokens: {
access: string
refresh: string
}
}
export interface RegisterUserData {
username: string
email: string
password: string
}
export interface RegisterMutationVariables {
user: RegisterUserData
}
export const REGISTER_MUTATION = gql` export const REGISTER_MUTATION = gql`
mutation register($user: UserCreateInput!) { mutation register($user: UserCreateInput!) {
tokens: authRegister(user: $user) { tokens: authRegister(user: $user) {

View File

@ -1,5 +1,11 @@
import { gql } from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminPagerFormEntryAdminQueryData {
id: string
email: string
username: string
}
export interface AdminPagerFormEntryQueryData { export interface AdminPagerFormEntryQueryData {
id: string id: string
created: string created: string
@ -7,11 +13,7 @@ export interface AdminPagerFormEntryQueryData {
title: string title: string
isLive: boolean isLive: boolean
language: string language: string
admin: { admin: AdminPagerFormEntryAdminQueryData
id: string
email: string
username: string
}
} }
export interface AdminPagerFormQueryData { export interface AdminPagerFormQueryData {

View File

@ -15,7 +15,6 @@
"@ant-design/icons": "^4.1.0", "@ant-design/icons": "^4.1.0",
"@apollo/react-hooks": "^3.1.5", "@apollo/react-hooks": "^3.1.5",
"@lifeomic/axios-fetch": "^1.4.2", "@lifeomic/axios-fetch": "^1.4.2",
"@types/swiper": "^5.3.1",
"antd": "^4.2.2", "antd": "^4.2.2",
"apollo-boost": "^0.4.9", "apollo-boost": "^0.4.9",
"axios": "^0.19.2", "axios": "^0.19.2",
@ -42,8 +41,13 @@
"swiper": "^5.4.1" "swiper": "^5.4.1"
}, },
"devDependencies": { "devDependencies": {
"@types/axios": "^0.14.0",
"@types/lifeomic__axios-fetch": "^1.4.0",
"@types/node": "^14.0.1", "@types/node": "^14.0.1",
"@types/node-fetch": "^2.5.7",
"@types/react": "^16.9.35", "@types/react": "^16.9.35",
"@types/styled-components": "^5.1.0",
"@types/swiper": "^5.3.1",
"@typescript-eslint/eslint-plugin": "^3.2.0", "@typescript-eslint/eslint-plugin": "^3.2.0",
"@typescript-eslint/parser": "^3.2.0", "@typescript-eslint/parser": "^3.2.0",
"commander": "^5.1.0", "commander": "^5.1.0",

View File

@ -13,13 +13,18 @@ import Head from 'next/head'
import React from 'react' import React from 'react'
import { wrapper } from 'store' import { wrapper } from 'store'
const { publicRuntimeConfig } = getConfig() const { publicRuntimeConfig } = getConfig() as {
publicRuntimeConfig: {
endpoint: string
}
}
const client = new ApolloClient({ const client = new ApolloClient({
uri: publicRuntimeConfig.endpoint, uri: publicRuntimeConfig.endpoint,
fetch: buildAxiosFetch(axios), // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-explicit-any
request: async (operation): Promise<void> => { fetch: buildAxiosFetch(axios) as any,
operation.setContext(await authConfig()) request: (operation): void => {
operation.setContext(authConfig())
}, },
}) })

View File

@ -112,10 +112,10 @@ const Index: NextPage = () => {
form.setFieldsValue(next) form.setFieldsValue(next)
setFields(next.form.fields) setFields(next.form.fields)
message.success(t('form:updated')) await message.success(t('form:updated'))
} catch (e) { } catch (e) {
console.error('failed to save', e) console.error('failed to save', e)
message.error(t('form:updateError')) await message.error(t('form:updateError'))
} }
setSaving(false) setSaving(false)
@ -134,7 +134,7 @@ const Index: NextPage = () => {
<Link <Link
key={'submissions'} key={'submissions'}
href={'/admin/forms/[id]/submissions'} href={'/admin/forms/[id]/submissions'}
as={`/admin/forms/${router.query.id}/submissions`} as={`/admin/forms/${router.query.id as string}/submissions`}
> >
<Button>{t('admin:submissions')}</Button> <Button>{t('admin:submissions')}</Button>
</Link>, </Link>,
@ -147,9 +147,9 @@ const Index: NextPage = () => {
<Form <Form
form={form} form={form}
onFinish={save} onFinish={save}
onFinishFailed={() => { onFinishFailed={async () => {
// TODO process errors // TODO process errors
message.error(t('validation:mandatoryFieldsMissing')) await message.error(t('validation:mandatoryFieldsMissing'))
}} }}
labelCol={{ labelCol={{
xs: { span: 24 }, xs: { span: 24 },

View File

@ -70,14 +70,14 @@ const Submissions: NextPage = () => {
{ {
title: t('submission:created'), title: t('submission:created'),
dataIndex: 'created', dataIndex: 'created',
render(date) { render(date: string) {
return <DateTime date={date} /> return <DateTime date={date} />
}, },
}, },
{ {
title: t('submission:lastModified'), title: t('submission:lastModified'),
dataIndex: 'lastModified', dataIndex: 'lastModified',
render(date) { render(date: string) {
return <TimeAgo date={date} /> return <TimeAgo date={date} />
}, },
}, },
@ -94,15 +94,24 @@ const Submissions: NextPage = () => {
{ {
href: '/admin/forms/[id]', href: '/admin/forms/[id]',
name: loading || !form ? t('form:loading') : t('form:mange', { title: form.title }), name: loading || !form ? t('form:loading') : t('form:mange', { title: form.title }),
as: `/admin/forms/${router.query.id}`, as: `/admin/forms/${router.query.id as string}`,
}, },
]} ]}
padded={false} padded={false}
extra={[ extra={[
<Link key={'edit'} href={'/admin/forms/[id]'} as={`/admin/forms/${router.query.id}`}> <Link
key={'edit'}
href={'/admin/forms/[id]'}
as={`/admin/forms/${router.query.id as string}`}
>
<Button>{t('submission:edit')}</Button> <Button>{t('submission:edit')}</Button>
</Link>, </Link>,
<Button key={'web'} href={`/form/${router.query.id}`} target={'_blank'} type={'primary'}> <Button
key={'web'}
href={`/form/${router.query.id as string}`}
target={'_blank'}
type={'primary'}
>
{t('submission:add')} {t('submission:add')}
</Button>, </Button>,
]} ]}
@ -120,9 +129,9 @@ const Submissions: NextPage = () => {
return record.percentageComplete > 0 return record.percentageComplete > 0
}, },
}} }}
onChange={(next) => { onChange={async (next) => {
setPagination(next) setPagination(next)
refetch() await refetch()
}} }}
/> />
</Structure> </Structure>

View File

@ -35,12 +35,12 @@ const Create: NextPage = () => {
}) })
).data ).data
message.success(t('form:created')) await message.success(t('form:created'))
router.replace('/admin/forms/[id]', `/admin/forms/${next.form.id}`) await router.replace('/admin/forms/[id]', `/admin/forms/${next.form.id}`)
} catch (e) { } catch (e) {
console.error('failed to save', e) console.error('failed to save', e)
message.error(t('form:creationError')) await message.error(t('form:creationError'))
} }
setSaving(false) setSaving(false)
@ -65,9 +65,9 @@ const Create: NextPage = () => {
<Form <Form
form={form} form={form}
onFinish={save} onFinish={save}
onFinishFailed={() => { onFinishFailed={async () => {
// TODO process errors // TODO process errors
message.error(t('validation:mandatoryFieldsMissing')) await message.error(t('validation:mandatoryFieldsMissing'))
}} }}
labelCol={{ labelCol={{
xs: { span: 24 }, xs: { span: 24 },

View File

@ -15,6 +15,7 @@ import { TimeAgo } from 'components/time.ago'
import { withAuth } from 'components/with.auth' import { withAuth } from 'components/with.auth'
import { import {
ADMIN_PAGER_FORM_QUERY, ADMIN_PAGER_FORM_QUERY,
AdminPagerFormEntryAdminQueryData,
AdminPagerFormEntryQueryData, AdminPagerFormEntryQueryData,
AdminPagerFormQueryData, AdminPagerFormQueryData,
AdminPagerFormQueryVariables, AdminPagerFormQueryVariables,
@ -56,7 +57,7 @@ const Index: NextPage = () => {
AdminFormDeleteMutationVariables AdminFormDeleteMutationVariables
>(ADMIN_FORM_DELETE_MUTATION) >(ADMIN_FORM_DELETE_MUTATION)
const deleteForm = async (form) => { const deleteForm = async (form: AdminFormDeleteMutationVariables) => {
try { try {
await executeDelete({ await executeDelete({
variables: { variables: {
@ -70,9 +71,9 @@ const Index: NextPage = () => {
setEntries(next) setEntries(next)
} }
message.success(t('form:deleted')) await message.success(t('form:deleted'))
} catch (e) { } catch (e) {
message.error(t('form:deleteError')) await message.error(t('form:deleteError'))
} }
} }
@ -80,7 +81,7 @@ const Index: NextPage = () => {
{ {
title: t('form:row.isLive'), title: t('form:row.isLive'),
dataIndex: 'isLive', dataIndex: 'isLive',
render(live) { render(live: boolean) {
return <FormIsLive isLive={live} /> return <FormIsLive isLive={live} />
}, },
}, },
@ -91,7 +92,7 @@ const Index: NextPage = () => {
{ {
title: t('form:row.admin'), title: t('form:row.admin'),
dataIndex: 'admin', dataIndex: 'admin',
render(user) { render(user: AdminPagerFormEntryAdminQueryData) {
return ( return (
<Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}> <Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}>
<Tooltip title={user.email}> <Tooltip title={user.email}>
@ -104,28 +105,28 @@ const Index: NextPage = () => {
{ {
title: t('form:row.language'), title: t('form:row.language'),
dataIndex: 'language', dataIndex: 'language',
render(lang) { render(lang: string) {
return t(`language:${lang}`) return t(`language:${lang}`)
}, },
}, },
{ {
title: t('form:row.created'), title: t('form:row.created'),
dataIndex: 'created', dataIndex: 'created',
render(date) { render(date: string) {
return <DateTime date={date} /> return <DateTime date={date} />
}, },
}, },
{ {
title: t('form:row.lastModified'), title: t('form:row.lastModified'),
dataIndex: 'lastModified', dataIndex: 'lastModified',
render(date) { render(date: string) {
return <TimeAgo date={date} /> return <TimeAgo date={date} />
}, },
}, },
{ {
title: t('form:row.menu'), title: t('form:row.menu'),
align: 'right', align: 'right',
render(row) { render(row: AdminPagerFormEntryQueryData) {
return ( return (
<Space> <Space>
<Link href={'/admin/forms/[id]/submissions'} as={`/admin/forms/${row.id}/submissions`}> <Link href={'/admin/forms/[id]/submissions'} as={`/admin/forms/${row.id}/submissions`}>
@ -182,9 +183,9 @@ const Index: NextPage = () => {
dataSource={entries} dataSource={entries}
rowKey={'id'} rowKey={'id'}
pagination={pagination} pagination={pagination}
onChange={(next) => { onChange={async (next) => {
setPagination(next) setPagination(next)
refetch() await refetch()
}} }}
/> />
</Structure> </Structure>

View File

@ -49,10 +49,10 @@ const Profile: NextPage = () => {
form.setFieldsValue(next) form.setFieldsValue(next)
message.success(t('profile:updated')) await message.success(t('profile:updated'))
} catch (e) { } catch (e) {
console.error('failed to save', e) console.error('failed to save', e)
message.error(t('profile:updateError')) await message.error(t('profile:updateError'))
} }
setSaving(false) setSaving(false)
@ -73,9 +73,9 @@ const Profile: NextPage = () => {
<Form <Form
form={form} form={form}
onFinish={save} onFinish={save}
onFinishFailed={() => { onFinishFailed={async () => {
// TODO process errors // TODO process errors
message.error(t('validation:mandatoryFieldsMissing')) await message.error(t('validation:mandatoryFieldsMissing'))
}} }}
labelCol={{ labelCol={{
xs: { span: 24 }, xs: { span: 24 },

View File

@ -53,10 +53,10 @@ const Index: NextPage = () => {
form.setFieldsValue(next) form.setFieldsValue(next)
message.success(t('user:updated')) await message.success(t('user:updated'))
} catch (e) { } catch (e) {
console.error('failed to save', e) console.error('failed to save', e)
message.error(t('user:updateError')) await message.error(t('user:updateError'))
} }
setSaving(false) setSaving(false)
@ -81,9 +81,9 @@ const Index: NextPage = () => {
<Form <Form
form={form} form={form}
onFinish={save} onFinish={save}
onFinishFailed={() => { onFinishFailed={async () => {
// TODO process errors // TODO process errors
message.error(t('validation:mandatoryFieldsMissing')) await message.error(t('validation:mandatoryFieldsMissing'))
}} }}
labelCol={{ labelCol={{
xs: { span: 24 }, xs: { span: 24 },

View File

@ -50,7 +50,7 @@ const Index: NextPage = () => {
AdminUserDeleteMutationVariables AdminUserDeleteMutationVariables
>(ADMIN_USER_DELETE_MUTATION) >(ADMIN_USER_DELETE_MUTATION)
const deleteUser = async (form) => { const deleteUser = async (form: AdminPagerUserEntryQueryData) => {
try { try {
await executeDelete({ await executeDelete({
variables: { variables: {
@ -63,9 +63,9 @@ const Index: NextPage = () => {
} else { } else {
setEntries(next) setEntries(next)
} }
message.success(t('user:deleted')) await message.success(t('user:deleted'))
} catch (e) { } catch (e) {
message.error(t('user:deleteError')) await message.error(t('user:deleteError'))
} }
} }
@ -73,27 +73,27 @@ const Index: NextPage = () => {
{ {
title: t('user:row.roles'), title: t('user:row.roles'),
dataIndex: 'roles', dataIndex: 'roles',
render(roles) { render(roles: string[]) {
return <UserRole roles={roles} /> return <UserRole roles={roles} />
}, },
}, },
{ {
title: t('user:row.email'), title: t('user:row.email'),
render(row) { render(row: AdminPagerUserEntryQueryData) {
return <Tag color={row.verifiedEmail ? 'lime' : 'volcano'}>{row.email}</Tag> return <Tag color={row.verifiedEmail ? 'lime' : 'volcano'}>{row.email}</Tag>
}, },
}, },
{ {
title: t('user:row.created'), title: t('user:row.created'),
dataIndex: 'created', dataIndex: 'created',
render(date) { render(date: string) {
return <DateTime date={date} /> return <DateTime date={date} />
}, },
}, },
{ {
title: t('user:row.menu'), title: t('user:row.menu'),
align: 'right', align: 'right',
render(row) { render(row: AdminPagerUserEntryQueryData) {
return ( return (
<Space> <Space>
<Link href={'/admin/users/[id]'} as={`/admin/users/${row.id}`}> <Link href={'/admin/users/[id]'} as={`/admin/users/${row.id}`}>
@ -130,9 +130,9 @@ const Index: NextPage = () => {
dataSource={entries} dataSource={entries}
rowKey={'id'} rowKey={'id'}
pagination={pagination} pagination={pagination}
onChange={(next) => { onChange={async (next) => {
setPagination(next) setPagination(next)
refetch() await refetch()
}} }}
/> />
</Structure> </Structure>

View File

@ -38,7 +38,9 @@ const Index: NextPage<Props> = () => {
} }
if (i18n.language !== data.form.language) { if (i18n.language !== data.form.language) {
i18n.changeLanguage(data.form.language) i18n
.changeLanguage(data.form.language)
.catch((e: Error) => console.error('failed to change language', e))
} }
}, [data]) }, [data])
@ -98,8 +100,8 @@ const Index: NextPage<Props> = () => {
key={field.id} key={field.id}
field={field} field={field}
design={design} design={design}
save={(values) => { save={async (values: { [key: string]: unknown }) => {
submission.setField(field.id, values[field.id]) await submission.setField(field.id, values[field.id])
if (data.form.fields.length === i + 1) { if (data.form.fields.length === i + 1) {
submission.finish() submission.finish()
@ -144,7 +146,7 @@ const Index: NextPage<Props> = () => {
) )
} }
Index.getInitialProps = async ({ query }) => { Index.getInitialProps = ({ query }) => {
return { return {
id: query.id as string, id: query.id as string,
} }

View File

@ -7,7 +7,12 @@ import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { LoadingPage } from '../components/loading.page' import { LoadingPage } from '../components/loading.page'
const { publicRuntimeConfig } = getConfig() // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { publicRuntimeConfig } = getConfig() as {
publicRuntimeConfig: {
spa: boolean
}
}
const Index: NextPage = () => { const Index: NextPage = () => {
const router = useRouter() const router = useRouter()
@ -17,14 +22,17 @@ const Index: NextPage = () => {
if (router.pathname !== window.location.pathname) { if (router.pathname !== window.location.pathname) {
let href = router.asPath let href = router.asPath
const as = router.asPath const as = router.asPath
const possible = [/(\/form\/)[^/]+/i, /(\/admin\/forms\/)[^/]+/i, /(\/admin\/users\/)[^/]+/i]
;[/(\/form\/)[^/]+/i, /(\/admin\/forms\/)[^/]+/i, /(\/admin\/users\/)[^/]+/i].forEach((r) => { possible.forEach((r) => {
if (r.test(as)) { if (r.test(as)) {
href = href.replace(r, '$1[id]') href = href.replace(r, '$1[id]')
} }
}) })
router.replace(href, as) router.replace(href, as).catch((e: Error) => {
console.error('failed redirect', e)
})
} }
}) })
@ -50,7 +58,7 @@ const Index: NextPage = () => {
width: 500, width: 500,
textAlign: 'center', textAlign: 'center',
}} }}
src={require('../assets/images/logo_white.png')} src={require('../assets/images/logo_white.png') as string}
/> />
<AuthFooter /> <AuthFooter />

View File

@ -4,7 +4,11 @@ import { useForm } from 'antd/lib/form/Form'
import { AuthFooter } from 'components/auth/footer' import { AuthFooter } from 'components/auth/footer'
import { AuthLayout } from 'components/auth/layout' import { AuthLayout } from 'components/auth/layout'
import { setAuth } from 'components/with.auth' import { setAuth } from 'components/with.auth'
import { LOGIN_MUTATION } from 'graphql/mutation/login.mutation' import {
LOGIN_MUTATION,
LoginMutationData,
LoginMutationVariables,
} from 'graphql/mutation/login.mutation'
import { NextPage } from 'next' import { NextPage } from 'next'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@ -16,29 +20,29 @@ const Index: NextPage = () => {
const [form] = useForm() const [form] = useForm()
const router = useRouter() const router = useRouter()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [login] = useMutation(LOGIN_MUTATION) const [login] = useMutation<LoginMutationData, LoginMutationVariables>(LOGIN_MUTATION)
const finish = async (data) => { const finish = async (data: LoginMutationVariables) => {
setLoading(true) setLoading(true)
try { try {
const result = await login({ const result = await login({
variables: data, variables: data,
}) })
await setAuth(result.data.tokens.access, result.data.tokens.refresh) setAuth(result.data.tokens.access, result.data.tokens.refresh)
message.success(t('login:welcomeBack')) await message.success(t('login:welcomeBack'))
router.push('/admin') await router.push('/admin')
} catch (e) { } catch (e) {
message.error(t('login:invalidLoginCredentials')) await message.error(t('login:invalidLoginCredentials'))
} }
setLoading(false) setLoading(false)
} }
const failed = () => { const failed = async () => {
message.error(t('validation:mandatoryFieldsMissing')) await message.error(t('validation:mandatoryFieldsMissing'))
} }
return ( return (
@ -55,7 +59,7 @@ const Index: NextPage = () => {
}} }}
> >
<img <img
src={require('../../assets/images/logo_white_small.png')} src={require('../../assets/images/logo_white_small.png') as string}
alt={'OhMyForm'} alt={'OhMyForm'}
style={{ style={{
display: 'block', display: 'block',

View File

@ -4,7 +4,12 @@ import { useForm } from 'antd/lib/form/Form'
import { AuthFooter } from 'components/auth/footer' import { AuthFooter } from 'components/auth/footer'
import { AuthLayout } from 'components/auth/layout' import { AuthLayout } from 'components/auth/layout'
import { setAuth } from 'components/with.auth' import { setAuth } from 'components/with.auth'
import { REGISTER_MUTATION } from 'graphql/mutation/register.mutation' import {
REGISTER_MUTATION,
RegisterMutationData,
RegisterMutationVariables,
RegisterUserData,
} from 'graphql/mutation/register.mutation'
import { NextPage } from 'next' import { NextPage } from 'next'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@ -20,9 +25,9 @@ const Register: NextPage = () => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const { data } = useQuery<SettingsQueryData>(SETTINGS_QUERY) const { data } = useQuery<SettingsQueryData>(SETTINGS_QUERY)
const [register] = useMutation(REGISTER_MUTATION) const [register] = useMutation<RegisterMutationData, RegisterMutationVariables>(REGISTER_MUTATION)
const finish = async (data) => { const finish = async (data: RegisterUserData) => {
setLoading(true) setLoading(true)
try { try {
@ -32,19 +37,19 @@ const Register: NextPage = () => {
}, },
}) })
await setAuth(result.data.tokens.access, result.data.tokens.refresh) setAuth(result.data.tokens.access, result.data.tokens.refresh)
message.success(t('register:welcome')) await message.success(t('register:welcome'))
router.push('/') await router.push('/')
} catch (e) { } catch (e) {
message.error(t('register:credentialsAlreadyInUse')) await message.error(t('register:credentialsAlreadyInUse'))
setLoading(false) setLoading(false)
} }
} }
const failed = () => { const failed = async () => {
message.error(t('validation:mandatoryFieldsMissing')) await message.error(t('validation:mandatoryFieldsMissing'))
} }
if (data && data.disabledSignUp.value) { if (data && data.disabledSignUp.value) {
@ -65,7 +70,7 @@ const Register: NextPage = () => {
}} }}
> >
<img <img
src={require('../assets/images/logo_white_small.png')} src={require('../assets/images/logo_white_small.png') as string}
alt={'OhMyForm'} alt={'OhMyForm'}
style={{ style={{
display: 'block', display: 'block',

View File

@ -8,10 +8,10 @@ export interface State {
auth: AuthState auth: AuthState
} }
const root = (state: State, action: AnyAction) => { const root = (state: State, action: AnyAction): State => {
switch (action.type) { switch (action.type) {
case HYDRATE: case HYDRATE:
return { ...state, ...action.payload } return { ...state, ...action.payload } as State
} }
const combined = combineReducers({ const combined = combineReducers({

View File

@ -1169,6 +1169,13 @@
resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.4.0.tgz#6f8732e1909fb12f6dca8a82b7fc4b79c6c8cfcd" resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.4.0.tgz#6f8732e1909fb12f6dca8a82b7fc4b79c6c8cfcd"
integrity sha512-AaEC/diS2two2JLsEItGhuAux8UfPo0o34/7l1SIw0t4SYunUYJsxM/Y55OR2ljiVn9ffKR1n1U9IEQhsK80jw== integrity sha512-AaEC/diS2two2JLsEItGhuAux8UfPo0o34/7l1SIw0t4SYunUYJsxM/Y55OR2ljiVn9ffKR1n1U9IEQhsK80jw==
"@types/axios@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46"
integrity sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=
dependencies:
axios "*"
"@types/color-name@^1.1.1": "@types/color-name@^1.1.1":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -1179,11 +1186,40 @@
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
"@types/hoist-non-react-statics@*":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/json-schema@^7.0.3": "@types/json-schema@^7.0.3":
version "7.0.4" version "7.0.4"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
"@types/lifeomic__axios-fetch@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@types/lifeomic__axios-fetch/-/lifeomic__axios-fetch-1.4.0.tgz#c3c6e6fd1af7507cbb992864891a809e1bc1d347"
integrity sha512-i6FGbhL840p71LxT4/6qq7LbWfpO1eMRFIfNAYEFqo3WkDI2O+BrJd7ac+FEBy818u+s6gGh5cqw6d1pmwJ3Dw==
dependencies:
"@types/node-fetch" "*"
axios "^0.19.0"
"@types/node-fetch@*", "@types/node-fetch@^2.5.7":
version "2.5.7"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "14.0.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.12.tgz#9c1d8ffb8084e8936603a6122a7649e40e68e04b"
integrity sha512-/sjzehvjkkpvLpYtN6/2dv5kg41otMGuHQUt9T2aiAuIfleCQRQHXXzF1eAw/qkZTj5Kcf4JSTf7EIizHocy6Q==
"@types/node@>=6", "@types/node@^14.0.1": "@types/node@>=6", "@types/node@^14.0.1":
version "14.0.5" version "14.0.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.5.tgz#3d03acd3b3414cf67faf999aed11682ed121f22b" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.5.tgz#3d03acd3b3414cf67faf999aed11682ed121f22b"
@ -1199,7 +1235,14 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
"@types/react@^16.9.35": "@types/react-native@*":
version "0.62.13"
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.62.13.tgz#c688c5ae03e426f927f7e1fa1a59cd067f35d1c2"
integrity sha512-hs4/tSABhcJx+J8pZhVoXHrOQD89WFmbs8QiDLNSA9zNrD46pityAuBWuwk1aMjPk9I3vC5ewkJroVRHgRIfdg==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^16.9.35":
version "16.9.35" version "16.9.35"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368"
integrity sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ== integrity sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ==
@ -1207,6 +1250,16 @@
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^2.2.0" csstype "^2.2.0"
"@types/styled-components@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.0.tgz#24d3412ba5395aa06e14fbc93c52f9454cebd0d6"
integrity sha512-ZFlLCuwF5r+4Vb7JUmd+Yr2S0UBdBGmI7ctFTgJMypIp3xOHI4LCFVn2dKMvpk6xDB2hLRykrEWMBwJEpUAUIQ==
dependencies:
"@types/hoist-non-react-statics" "*"
"@types/react" "*"
"@types/react-native" "*"
csstype "^2.2.0"
"@types/swiper@^5.3.1": "@types/swiper@^5.3.1":
version "5.3.1" version "5.3.1"
resolved "https://registry.yarnpkg.com/@types/swiper/-/swiper-5.3.1.tgz#cc0c7c6f84e330ff50bec0a3ed0d172751e9d1fc" resolved "https://registry.yarnpkg.com/@types/swiper/-/swiper-5.3.1.tgz#cc0c7c6f84e330ff50bec0a3ed0d172751e9d1fc"
@ -1836,7 +1889,7 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
axios@^0.19.2: axios@*, axios@^0.19.0, axios@^0.19.2:
version "0.19.2" version "0.19.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
@ -2411,7 +2464,7 @@ color@^3.0.0:
color-convert "^1.9.1" color-convert "^1.9.1"
color-string "^1.5.2" color-string "^1.5.2"
combined-stream@^1.0.6: combined-stream@^1.0.6, combined-stream@^1.0.8:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@ -3545,6 +3598,15 @@ form-data@^2.3.3:
combined-stream "^1.0.6" combined-stream "^1.0.6"
mime-types "^2.1.12" mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
fragment-cache@^0.2.1: fragment-cache@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"