apply eslint

This commit is contained in:
Michael Schramm 2020-06-09 11:54:50 +02:00
parent c2608f047e
commit 8d81390c83
109 changed files with 1727 additions and 1707 deletions

View File

@ -2,21 +2,25 @@ module.exports = {
root: true, root: true,
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
ecmaFeatures: { jsx: true } ecmaFeatures: { jsx: true },
}, },
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:react/recommended', 'plugin:react/recommended',
"plugin:jsx-a11y/recommended", 'plugin:jsx-a11y/recommended',
'prettier/@typescript-eslint', 'prettier/@typescript-eslint',
'plugin:prettier/recommended', 'plugin:prettier/recommended',
], ],
rules: { rules: {
'prettier/prettier': [ 'prettier/prettier': ['error', {}, { usePrettierrc: true }],
"error", {}, { "usePrettierrc": true }
],
'react/prop-types': 'off', 'react/prop-types': 'off',
'@typescript-eslint/no-empty-interface': 'off',
}, },
}; settings: {
react: {
version: 'detect',
},
},
}

View File

@ -9,4 +9,3 @@ cache:
script: script:
- yarn - yarn
- yarn lint - yarn lint
- yarn build

View File

@ -1,13 +1,12 @@
import {UpOutlined} from '@ant-design/icons/lib' import { useQuery } from '@apollo/react-hooks'
import {useQuery} from '@apollo/react-hooks' import { Button, Select } from 'antd'
import {Button, Menu, Select} from 'antd'
import Link from 'next/link' import Link from 'next/link'
import {useRouter} from 'next/router' import { useRouter } from 'next/router'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {SETTINGS_QUERY, SettingsQueryData} from '../../graphql/query/settings.query' import { SETTINGS_QUERY, SettingsQueryData } from '../../graphql/query/settings.query'
import {languages} from '../../i18n' import { languages } from '../../i18n'
import {clearAuth, withAuth} from '../with.auth' import { clearAuth, withAuth } from '../with.auth'
interface Props { interface Props {
me?: { me?: {
@ -16,10 +15,10 @@ interface Props {
} }
} }
const AuthFooterInner: React.FC<Props> = props => { const AuthFooterInner: React.FC<Props> = (props) => {
const { t, i18n } = useTranslation() const { t, i18n } = useTranslation()
const router = useRouter() const router = useRouter()
const {data} = useQuery<SettingsQueryData>(SETTINGS_QUERY) const { data } = useQuery<SettingsQueryData>(SETTINGS_QUERY)
const logout = () => { const logout = () => {
clearAuth() clearAuth()
@ -38,67 +37,55 @@ const AuthFooterInner: React.FC<Props> = props => {
}} }}
> >
<Link href={'/admin'}> <Link href={'/admin'}>
<Button <Button type={'link'} ghost>
type={'link'}
ghost
>
{t('admin')} {t('admin')}
</Button> </Button>
</Link> </Link>
{props.me ? ( {props.me
[ ? [
<span style={{color: '#FFF'}} key={'user'}> <span style={{ color: '#FFF' }} key={'user'}>
Hi, {props.me.username} Hi, {props.me.username}
</span>, </span>,
<Button <Button key={'logout'} type={'link'} ghost onClick={logout}>
key={'logout'} {t('logout')}
type={'link'} </Button>,
ghost ]
onClick={logout} : [
> <Link href={'/login'} key={'login'}>
{t('logout')} <Button type={'link'} ghost>
</Button> {t('login')}
] </Button>
): ( </Link>,
[ <Link href={'/register'} key={'register'}>
<Link href={'/login'} key={'login'}> <Button type={'link'} ghost disabled={data ? data.disabledSignUp.value : false}>
<Button {t('register')}
type={'link'} </Button>
ghost </Link>,
> ]}
{t('login')} <div style={{ flex: 1 }} />
</Button>
</Link>,
<Link href={'/register'} key={'register'}>
<Button
type={'link'}
ghost
disabled={data ? data.disabledSignUp.value : false}
>
{t('register')}
</Button>
</Link>
]
)}
<div style={{flex: 1}} />
<Select <Select
bordered={false} bordered={false}
value={i18n.language.replace(/-.*/, '')} value={i18n.language.replace(/-.*/, '')}
onChange={next => i18n.changeLanguage(next)} onChange={(next) => i18n.changeLanguage(next)}
style={{ style={{
color: '#FFF', color: '#FFF',
}} }}
suffixIcon={false} suffixIcon={false}
> >
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )} {languages.map((language) => (
<Select.Option value={language} key={language}>
{t(`language:${language}`)}
</Select.Option>
))}
</Select> </Select>
<Button <Button
type={'link'} type={'link'}
target={'_blank'} target={'_blank'}
rel={'noreferrer'}
ghost ghost
href={'https://www.ohmyform.com'} href={'https://www.ohmyform.com'}
style={{ style={{
color: '#FFF' color: '#FFF',
}} }}
> >
&copy; OhMyForm &copy; OhMyForm

View File

@ -1,17 +1,19 @@
import {Layout, Spin} from 'antd' import { Layout, Spin } from 'antd'
import React from 'react' import React from 'react'
interface Props { interface Props {
loading?: boolean loading?: boolean
} }
export const AuthLayout: React.FC<Props> = props => { export const AuthLayout: React.FC<Props> = (props) => {
return ( return (
<Spin spinning={props.loading}> <Spin spinning={props.loading}>
<Layout style={{ <Layout
height: '100vh', style={{
background: '#437fdc' height: '100vh',
}}> background: '#437fdc',
}}
>
{props.children} {props.children}
</Layout> </Layout>
</Spin> </Spin>

View File

@ -1,4 +1,3 @@
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)
@ -7,19 +6,24 @@ 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 => {
const keys: Array<any> = Object.keys(obj); // eslint-disable-next-line @typescript-eslint/no-explicit-any
const newObj: any = {}; const keys: Array<any> = Object.keys(obj)
// eslint-disable-next-line @typescript-eslint/no-explicit-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) {
const val: any = obj[i]; // eslint-disable-next-line @typescript-eslint/no-explicit-any
if (val instanceof Date) newObj[i] = val; const val: any = obj[i]
else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key); if (val instanceof Date) newObj[i] = val
else if (typeof val === 'object' && val !== null) newObj[i] = omitDeep(val, key); else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key)
else newObj[i] = val; else if (typeof val === 'object' && val !== null) newObj[i] = omitDeep(val, key)
else newObj[i] = val
} }
}); })
return newObj; return newObj
} }
export const cleanInput = <T>(obj: T): T => { export const cleanInput = <T>(obj: T): T => {

View File

@ -7,13 +7,15 @@ interface Props {
hideTime?: boolean hideTime?: boolean
} }
export const DateTime: React.FC<Props> = props => { export const DateTime: React.FC<Props> = (props) => {
const format = props.hideTime ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm' const format = props.hideTime ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm'
return ( return (
<div style={{ <div
display: 'inline-block' style={{
}}> display: 'inline-block',
}}
>
{dayjs(props.date).format(format)} {dayjs(props.date).format(format)}
</div> </div>
) )

View File

@ -2,13 +2,15 @@ import React from 'react'
export const ErrorPage: React.FC = () => { export const ErrorPage: React.FC = () => {
return ( return (
<div style={{ <div
height: '100vh', style={{
justifyContent: 'center', height: '100vh',
alignItems: 'center', justifyContent: 'center',
display: 'flex', alignItems: 'center',
flexDirection: 'column', display: 'flex',
}}> flexDirection: 'column',
}}
>
<h1>ERROR</h1> <h1>ERROR</h1>
<p>there was an error with your request</p> <p>there was an error with your request</p>
</div> </div>

View File

@ -1,10 +1,10 @@
import {Form, Input, Select, Switch, Tabs} from 'antd' import { Form, Input, Select, Switch, Tabs } from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import { TabPaneProps } from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {languages} from '../../../i18n' import { languages } from '../../../i18n'
export const BaseDataTab: React.FC<TabPaneProps> = props => { export const BaseDataTab: React.FC<TabPaneProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -41,7 +41,11 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
]} ]}
> >
<Select> <Select>
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )} {languages.map((language) => (
<Select.Option value={language} key={language}>
{t(`language:${language}`)}
</Select.Option>
))}
</Select> </Select>
</Form.Item> </Form.Item>
@ -52,7 +56,6 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
</Tabs.TabPane> </Tabs.TabPane>
) )
} }

View File

@ -1,18 +1,15 @@
import {Form, Input, Tabs} from 'antd' import { Form, Input, Tabs } from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import { TabPaneProps } from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {InputColor} from '../../input/color' import { InputColor } from '../../input/color'
export const DesignTab: React.FC<TabPaneProps> = props => { export const DesignTab: React.FC<TabPaneProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.Item <Form.Item label={t('form:design.font')} name={['form', 'design', 'font']}>
label={t('form:design.font')}
name={['form', 'design', 'font']}
>
<Input /> <Input />
</Form.Item> </Form.Item>
@ -23,8 +20,12 @@ export const DesignTab: React.FC<TabPaneProps> = props => {
'buttonColor', 'buttonColor',
'buttonActiveColor', 'buttonActiveColor',
'buttonTextColor', 'buttonTextColor',
].map(name => ( ].map((name) => (
<Form.Item key={name} label={t(`form:design.${name}`)} name={['form', 'design', 'colors', name]}> <Form.Item
key={name}
label={t(`form:design.${name}`)}
name={['form', 'design', 'colors', name]}
>
<InputColor /> <InputColor />
</Form.Item> </Form.Item>
))} ))}

View File

@ -1,11 +1,11 @@
import {DeleteOutlined, PlusOutlined} from '@ant-design/icons/lib' import { DeleteOutlined, PlusOutlined } from '@ant-design/icons/lib'
import {Button, Card, Form, Input, Switch, Tabs} from 'antd' import { Button, Card, Form, Input, Switch, Tabs } from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import { TabPaneProps } from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {InputColor} from '../../input/color' import { InputColor } from '../../input/color'
export const EndPageTab: React.FC<TabPaneProps> = props => { export const EndPageTab: React.FC<TabPaneProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -18,17 +18,11 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t('form:endPage.title')} name={['form', 'endPage', 'title']}>
label={t('form:endPage.title')}
name={['form', 'endPage', 'title']}
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t('form:endPage.paragraph')} name={['form', 'endPage', 'paragraph']}>
label={t('form:endPage.paragraph')}
name={['form', 'endPage', 'paragraph']}
>
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
@ -39,54 +33,65 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.List <Form.List name={['form', 'endPage', 'buttons']}>
name={['form', 'endPage', 'buttons']}
>
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
<Form.Item <Form.Item
wrapperCol={{ wrapperCol={{
sm: { offset: index === 0 ? 0 : 6 }, sm: { offset: index === 0 ? 0 : 6 },
}} }}
label={index === 0 ? t('form:endPage.buttons') : ''} label={index === 0 ? t('form:endPage.buttons') : ''}
key={field.key} key={field.key}
> >
<Card <Card actions={[<DeleteOutlined key={'delete'} onClick={() => remove(index)} />]}>
actions={[ <Form.Item
<DeleteOutlined key={'delete'} onClick={() => remove(index)} /> label={t('form:endPage.url')}
]} name={[field.key, 'url']}
rules={[{ type: 'url', message: t('validation:invalidUrl') }]}
labelCol={{ span: 6 }}
> >
<Form.Item <Input />
label={t('form:endPage.url')} </Form.Item>
name={[field.key, 'url']} <Form.Item
rules={[ label={t('form:endPage.action')}
{type: 'url', message: t('validation:invalidUrl')} name={[field.key, 'action']}
]} labelCol={{ span: 6 }}
labelCol={{ span: 6 }} >
> <Input />
<Input /> </Form.Item>
</Form.Item> <Form.Item
<Form.Item label={t('form:endPage.action')} name={[field.key, 'action']} labelCol={{ span: 6 }}> label={t('form:endPage.text')}
<Input /> name={[field.key, 'text']}
</Form.Item> labelCol={{ span: 6 }}
<Form.Item label={t('form:endPage.text')} name={[field.key, 'text']} labelCol={{ span: 6 }}> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t('form:endPage.bgColor')} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}> <Form.Item
<InputColor /> label={t('form:endPage.bgColor')}
</Form.Item> name={[field.key, 'bgColor']}
<Form.Item label={t('form:endPage.activeColor')} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}> labelCol={{ span: 6 }}
<InputColor /> >
</Form.Item> <InputColor />
<Form.Item label={t('form:endPage.color')} name={[field.key, 'color']} labelCol={{ span: 6 }}> </Form.Item>
<InputColor /> <Form.Item
</Form.Item> label={t('form:endPage.activeColor')}
</Card> name={[field.key, 'activeColor']}
</Form.Item> labelCol={{ span: 6 }}
) >
)} <InputColor />
</Form.Item>
<Form.Item
label={t('form:endPage.color')}
name={[field.key, 'color']}
labelCol={{ span: 6 }}
>
<InputColor />
</Form.Item>
</Card>
</Form.Item>
))}
<Form.Item <Form.Item
wrapperCol={{ wrapperCol={{
sm: { offset: 6 }, sm: { offset: 6 },
@ -95,7 +100,7 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
<Button <Button
type="dashed" type="dashed"
onClick={() => { onClick={() => {
add(); add()
}} }}
style={{ width: '60%' }} style={{ width: '60%' }}
> >

View File

@ -1,50 +1,47 @@
import {DeleteOutlined} from '@ant-design/icons/lib' import { DeleteOutlined } from '@ant-design/icons/lib'
import {Button, Card, Checkbox, Form, Input, Popconfirm, Tag} from 'antd' import { Button, Card, Checkbox, Form, Input, Popconfirm, Tag } from 'antd'
import {FormInstance} from 'antd/lib/form' import { FormInstance } from 'antd/lib/form'
import {FieldData} from 'rc-field-form/lib/interface' import { FieldData } from 'rc-field-form/lib/interface'
import React, {useEffect, useState} from 'react' import React, { useEffect, 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'
import {adminTypes} from './types' import { adminTypes } from './types'
import {TextType} from './types/text.type' import { TextType } from './types/text.type'
interface Props { interface Props {
form: FormInstance form: FormInstance
fields: AdminFormFieldFragment[] fields: AdminFormFieldFragment[]
onChangeFields: (fields: AdminFormFieldFragment[]) => any onChangeFields: (fields: AdminFormFieldFragment[]) => void
field: FieldData field: FieldData
remove: (index: number) => void remove: (index: number) => void
index: number index: number
} }
export const FieldCard: React.FC<Props> = props => { export const FieldCard: React.FC<Props> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { const { form, field, fields, onChangeFields, remove, index } = props
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'])
const TypeComponent = adminTypes[type] || TextType const TypeComponent = adminTypes[type] || TextType
const [nextTitle, setNextTitle] = useState(form.getFieldValue(['form', 'fields', field.name as string, 'title'])) const [nextTitle, setNextTitle] = useState(
form.getFieldValue(['form', 'fields', field.name as string, 'title'])
)
useEffect(() => { useEffect(() => {
const id = setTimeout(() => { const id = setTimeout(() => {
onChangeFields(fields.map((field, i) => { onChangeFields(
if (i === index) { fields.map((field, i) => {
return { if (i === index) {
...field, return {
title: nextTitle, ...field,
title: nextTitle,
}
} else {
return field
} }
} else { })
return field )
}
}))
}, 500) }, 500)
return () => clearTimeout(id) return () => clearTimeout(id)
@ -54,7 +51,7 @@ export const FieldCard: React.FC<Props> = props => {
<Card <Card
title={nextTitle} title={nextTitle}
type={'inner'} type={'inner'}
extra={( extra={
<div> <div>
<Tag color={'blue'}>{t(`type:${type}.name`)}</Tag> <Tag color={'blue'}>{t(`type:${type}.name`)}</Tag>
<Popconfirm <Popconfirm
@ -67,24 +64,24 @@ export const FieldCard: React.FC<Props> = props => {
onChangeFields(fields.filter((e, i) => i !== index)) onChangeFields(fields.filter((e, i) => i !== index))
}} }}
> >
<Button danger><DeleteOutlined /></Button> <Button danger>
<DeleteOutlined />
</Button>
</Popconfirm> </Popconfirm>
</div> </div>
)} }
actions={[ actions={[<DeleteOutlined key={'delete'} onClick={() => remove(index)} />]}
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
]}
> >
<Form.Item name={[field.name as string, 'type']} noStyle><Input type={'hidden'} /></Form.Item> <Form.Item name={[field.name as string, 'type']} noStyle>
<Input type={'hidden'} />
</Form.Item>
<Form.Item <Form.Item
label={t('type:title')} label={t('type:title')}
name={[field.name as string, 'title']} name={[field.name as string, 'title']}
rules={[ rules={[{ required: true, message: 'Title is required' }]}
{ required: true, message: 'Title is required' }
]}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input onChange={e => setNextTitle(e.target.value)}/> <Input onChange={(e) => setNextTitle(e.target.value)} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t('type:description')} label={t('type:description')}
@ -103,10 +100,7 @@ export const FieldCard: React.FC<Props> = props => {
<Checkbox /> <Checkbox />
</Form.Item> </Form.Item>
<TypeComponent <TypeComponent field={field} form={form} />
field={field}
form={form}
/>
</Card> </Card>
) )
} }

View File

@ -1,86 +1,90 @@
import {PlusOutlined} from '@ant-design/icons/lib' 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 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'
import {FieldCard} from './field.card' import { FieldCard } from './field.card'
import {adminTypes} from './types' import { adminTypes } from './types'
interface Props extends TabPaneProps { interface Props extends TabPaneProps {
form: FormInstance form: FormInstance
fields: AdminFormFieldFragment[] fields: AdminFormFieldFragment[]
onChangeFields: (fields: AdminFormFieldFragment[]) => any onChangeFields: (fields: AdminFormFieldFragment[]) => void
} }
export const FieldsTab: React.FC<Props> = props => { export const FieldsTab: React.FC<Props> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const [nextType, setNextType] = useState('textfield') const [nextType, setNextType] = useState('textfield')
const renderType = useCallback((field, index, remove) => { const renderType = useCallback(
return ( (field, index, remove) => {
<FieldCard return (
form={props.form} <FieldCard
field={field} form={props.form}
index={index} field={field}
remove={remove} index={index}
fields={props.fields} remove={remove}
onChangeFields={props.onChangeFields} fields={props.fields}
/> onChangeFields={props.onChangeFields}
) />
}, [props.fields]) )
},
[props.fields]
)
const addField = useCallback((add, index) => { const addField = useCallback(
return ( (add, index) => {
<Form.Item return (
wrapperCol={{span: 24}} <Form.Item wrapperCol={{ span: 24 }}>
> <Space
<Space style={{
style={{ width: '100%',
width: '100%', justifyContent: 'flex-end',
justifyContent: 'flex-end',
}}
>
<Select value={nextType} onChange={e => setNextType(e)} style={{ minWidth: 200 }}>
{Object.keys(adminTypes).map(type => <Select.Option value={type} key={type}>{t(`type:${type}.name`)}</Select.Option> )}
</Select>
<Button
type="dashed"
onClick={() => {
const defaults: AdminFormFieldFragment = {
logicJump: {
enabled: false,
},
options: [],
id: `NEW-${Date.now()}`,
type: nextType,
title: '',
description: '',
required: false,
value: ''
}
add(defaults)
const next = [...props.fields]
next.splice(index, 0, defaults)
props.onChangeFields(next)
}} }}
> >
<PlusOutlined /> Add Field <Select value={nextType} onChange={(e) => setNextType(e)} style={{ minWidth: 200 }}>
</Button> {Object.keys(adminTypes).map((type) => (
</Space> <Select.Option value={type} key={type}>
</Form.Item> {t(`type:${type}.name`)}
) </Select.Option>
}, [props.fields, nextType]) ))}
</Select>
<Button
type="dashed"
onClick={() => {
const defaults: AdminFormFieldFragment = {
logicJump: {
enabled: false,
},
options: [],
id: `NEW-${Date.now()}`,
type: nextType,
title: '',
description: '',
required: false,
value: '',
}
add(defaults)
const next = [...props.fields]
next.splice(index, 0, defaults)
props.onChangeFields(next)
}}
>
<PlusOutlined /> Add Field
</Button>
</Space>
</Form.Item>
)
},
[props.fields, nextType]
)
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.List name={['form', 'fields']}>
<Form.List
name={['form', 'fields']}
>
{(fields, { add, remove, move }) => { {(fields, { add, remove, move }) => {
const addAndMove = (index) => (defaults) => { const addAndMove = (index) => (defaults) => {
add(defaults) add(defaults)
@ -102,7 +106,6 @@ export const FieldsTab: React.FC<Props> = props => {
) )
}} }}
</Form.List> </Form.List>
</Tabs.TabPane> </Tabs.TabPane>
) )
} }

View File

@ -1,22 +1,26 @@
import {CheckCircleOutlined, CloseCircleOutlined} from '@ant-design/icons/lib' import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons/lib'
import React from 'react' import React from 'react'
interface Props { interface Props {
isLive: boolean isLive: boolean
} }
export const FormIsLive: React.FC<Props> = props => { export const FormIsLive: React.FC<Props> = (props) => {
if (props.isLive) { if (props.isLive) {
return ( return (
<CheckCircleOutlined style={{ <CheckCircleOutlined
color: 'green' style={{
}} /> color: 'green',
}}
/>
) )
} }
return ( return (
<CloseCircleOutlined style={{ <CloseCircleOutlined
color: 'red' style={{
}} /> color: 'red',
}}
/>
) )
} }

View File

@ -1,17 +1,17 @@
import {InfoCircleOutlined} from '@ant-design/icons/lib' import { InfoCircleOutlined } from '@ant-design/icons/lib'
import {Form, Input, Select, Switch, Tabs} from 'antd' import { Form, Input, Select, Switch, 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 React, {useEffect, useState} from 'react' import React, { useEffect, useState } from 'react'
import {Trans, useTranslation} from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment' import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
interface Props extends TabPaneProps { interface Props extends TabPaneProps {
form: FormInstance form: FormInstance
fields: AdminFormFieldFragment[] fields: AdminFormFieldFragment[]
} }
export const RespondentNotificationsTab: React.FC<Props> = props => { export const RespondentNotificationsTab: React.FC<Props> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const [enabled, setEnabled] = useState<boolean>() const [enabled, setEnabled] = useState<boolean>()
@ -33,7 +33,7 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
const groups = {} const groups = {}
props.fields.forEach(field => { props.fields.forEach((field) => {
if (!groups[field.type]) { if (!groups[field.type]) {
groups[field.type] = [] groups[field.type] = []
} }
@ -47,7 +47,7 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
name={['form', 'respondentNotifications', 'enabled']} name={['form', 'respondentNotifications', 'enabled']}
valuePropName={'checked'} valuePropName={'checked'}
> >
<Switch onChange={e => setEnabled(e.valueOf())} /> <Switch onChange={(e) => setEnabled(e.valueOf())} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -72,12 +72,13 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
message: t('validation:templateRequired'), message: t('validation:templateRequired'),
}, },
]} ]}
extra={( extra={
<div> <div>
<Trans>form:respondentNotifications.htmlTemplateInfo</Trans> <Trans>form:respondentNotifications.htmlTemplateInfo</Trans>
<a <a
href={'https://mjml.io/try-it-live'} href={'https://mjml.io/try-it-live'}
target={'_blank'} target={'_blank'}
rel={'noreferrer'}
style={{ style={{
marginLeft: 16, marginLeft: 16,
}} }}
@ -85,7 +86,7 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
<InfoCircleOutlined /> <InfoCircleOutlined />
</a> </a>
</div> </div>
)} }
> >
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
@ -102,10 +103,12 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
]} ]}
> >
<Select> <Select>
{Object.keys(groups).map(key => ( {Object.keys(groups).map((key) => (
<Select.OptGroup label={key.toUpperCase()} key={key}> <Select.OptGroup label={key.toUpperCase()} key={key}>
{groups[key].map(field => ( {groups[key].map((field) => (
<Select.Option value={field.id} key={field.id}>{field.title}</Select.Option> <Select.Option value={field.id} key={field.id}>
{field.title}
</Select.Option>
))} ))}
</Select.OptGroup> </Select.OptGroup>
))} ))}

View File

@ -1,17 +1,17 @@
import {InfoCircleOutlined} from '@ant-design/icons/lib' import { InfoCircleOutlined } from '@ant-design/icons/lib'
import {Form, Input, Select, Switch, Tabs} from 'antd' import { Form, Input, Select, Switch, 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 React, {useEffect, useState} from 'react' import React, { useEffect, useState } from 'react'
import {Trans, useTranslation} from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment' import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
interface Props extends TabPaneProps { interface Props extends TabPaneProps {
form: FormInstance form: FormInstance
fields: AdminFormFieldFragment[] fields: AdminFormFieldFragment[]
} }
export const SelfNotificationsTab: React.FC<Props> = props => { export const SelfNotificationsTab: React.FC<Props> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const [enabled, setEnabled] = useState<boolean>() const [enabled, setEnabled] = useState<boolean>()
@ -31,7 +31,7 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
}, [enabled]) }, [enabled])
const groups = {} const groups = {}
props.fields.forEach(field => { props.fields.forEach((field) => {
if (!groups[field.type]) { if (!groups[field.type]) {
groups[field.type] = [] groups[field.type] = []
} }
@ -45,7 +45,7 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
name={['form', 'selfNotifications', 'enabled']} name={['form', 'selfNotifications', 'enabled']}
valuePropName={'checked'} valuePropName={'checked'}
> >
<Switch onChange={e => setEnabled(e.valueOf())} /> <Switch onChange={(e) => setEnabled(e.valueOf())} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -70,12 +70,13 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
message: t('validation:templateRequired'), message: t('validation:templateRequired'),
}, },
]} ]}
extra={( extra={
<div> <div>
<Trans>form:selfNotifications.htmlTemplateInfo</Trans> <Trans>form:selfNotifications.htmlTemplateInfo</Trans>
<a <a
href={'https://mjml.io/try-it-live'} href={'https://mjml.io/try-it-live'}
target={'_blank'} target={'_blank'}
rel={'noreferrer'}
style={{ style={{
marginLeft: 16, marginLeft: 16,
}} }}
@ -83,7 +84,7 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
<InfoCircleOutlined /> <InfoCircleOutlined />
</a> </a>
</div> </div>
)} }
> >
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
@ -94,10 +95,12 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
extra={t('form:selfNotifications.fromFieldInfo')} extra={t('form:selfNotifications.fromFieldInfo')}
> >
<Select> <Select>
{Object.keys(groups).map(key => ( {Object.keys(groups).map((key) => (
<Select.OptGroup label={key.toUpperCase()} key={key}> <Select.OptGroup label={key.toUpperCase()} key={key}>
{groups[key].map(field => ( {groups[key].map((field) => (
<Select.Option value={field.id} key={field.id}>{field.title}</Select.Option> <Select.Option value={field.id} key={field.id}>
{field.title}
</Select.Option>
))} ))}
</Select.OptGroup> </Select.OptGroup>
))} ))}

View File

@ -1,11 +1,11 @@
import {DeleteOutlined, PlusOutlined} from '@ant-design/icons/lib' import { DeleteOutlined, PlusOutlined } from '@ant-design/icons/lib'
import {Button, Card, Form, Input, Switch, Tabs} from 'antd' import { Button, Card, Form, Input, Switch, Tabs } from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import { TabPaneProps } from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {InputColor} from '../../input/color' import { InputColor } from '../../input/color'
export const StartPageTab: React.FC<TabPaneProps> = props => { export const StartPageTab: React.FC<TabPaneProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -18,17 +18,11 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t('form:startPage.title')} name={['form', 'startPage', 'title']}>
label={t('form:startPage.title')}
name={['form', 'startPage', 'title']}
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t('form:startPage.paragraph')} name={['form', 'startPage', 'paragraph']}>
label={t('form:startPage.paragraph')}
name={['form', 'startPage', 'paragraph']}
>
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
@ -39,54 +33,65 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.List <Form.List name={['form', 'startPage', 'buttons']}>
name={['form', 'startPage', 'buttons']}
>
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
<Form.Item <Form.Item
wrapperCol={{ wrapperCol={{
sm: { offset: index === 0 ? 0 : 6 }, sm: { offset: index === 0 ? 0 : 6 },
}} }}
label={index === 0 ? t('form:startPage.buttons') : ''} label={index === 0 ? t('form:startPage.buttons') : ''}
key={field.key} key={field.key}
> >
<Card <Card actions={[<DeleteOutlined key={'delete'} onClick={() => remove(index)} />]}>
actions={[ <Form.Item
<DeleteOutlined key={'delete'} onClick={() => remove(index)} /> label={t('form:startPage.url')}
]} name={[field.key, 'url']}
rules={[{ type: 'url', message: t('validation:invalidUrl') }]}
labelCol={{ span: 6 }}
> >
<Form.Item <Input />
label={t('form:startPage.url')} </Form.Item>
name={[field.key, 'url']} <Form.Item
rules={[ label={t('form:startPage.action')}
{type: 'url', message: t('validation:invalidUrl')} name={[field.key, 'action']}
]} labelCol={{ span: 6 }}
labelCol={{ span: 6 }} >
> <Input />
<Input /> </Form.Item>
</Form.Item> <Form.Item
<Form.Item label={t('form:startPage.action')} name={[field.key, 'action']} labelCol={{ span: 6 }}> label={t('form:startPage.text')}
<Input /> name={[field.key, 'text']}
</Form.Item> labelCol={{ span: 6 }}
<Form.Item label={t('form:startPage.text')} name={[field.key, 'text']} labelCol={{ span: 6 }}> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t('form:startPage.bgColor')} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}> <Form.Item
<InputColor /> label={t('form:startPage.bgColor')}
</Form.Item> name={[field.key, 'bgColor']}
<Form.Item label={t('form:startPage.activeColor')} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}> labelCol={{ span: 6 }}
<InputColor /> >
</Form.Item> <InputColor />
<Form.Item label={t('form:startPage.color')} name={[field.key, 'color']} labelCol={{ span: 6 }}> </Form.Item>
<InputColor /> <Form.Item
</Form.Item> label={t('form:startPage.activeColor')}
</Card> name={[field.key, 'activeColor']}
</Form.Item> labelCol={{ span: 6 }}
) >
)} <InputColor />
</Form.Item>
<Form.Item
label={t('form:startPage.color')}
name={[field.key, 'color']}
labelCol={{ span: 6 }}
>
<InputColor />
</Form.Item>
</Card>
</Form.Item>
))}
<Form.Item <Form.Item
wrapperCol={{ wrapperCol={{
sm: { offset: 6 }, sm: { offset: 6 },
@ -95,7 +100,7 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
<Button <Button
type="dashed" type="dashed"
onClick={() => { onClick={() => {
add(); add()
}} }}
style={{ width: '60%' }} style={{ width: '60%' }}
> >

View File

@ -1,11 +1,11 @@
import {Descriptions, Table} from 'antd' import { Descriptions, Table } from 'antd'
import {ColumnsType} from 'antd/lib/table/interface' import { ColumnsType } from 'antd/lib/table/interface'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
AdminPagerSubmissionEntryFieldQueryData, AdminPagerSubmissionEntryFieldQueryData,
AdminPagerSubmissionEntryQueryData, AdminPagerSubmissionEntryQueryData,
AdminPagerSubmissionFormQueryData AdminPagerSubmissionFormQueryData,
} from '../../../graphql/query/admin.pager.submission.query' } from '../../../graphql/query/admin.pager.submission.query'
interface Props { interface Props {
@ -13,24 +13,23 @@ interface Props {
submission: AdminPagerSubmissionEntryQueryData submission: AdminPagerSubmissionEntryQueryData
} }
export const SubmissionValues: React.FC<Props> = props => { export const SubmissionValues: React.FC<Props> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
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 ? '*' : ''}`
} }
return `${row.id}` return `${row.id}`
} },
}, },
{ {
title: t('submission:value'), title: t('submission:value'),
render: row => { render: (row) => {
try { try {
const data = JSON.parse(row.value) const data = JSON.parse(row.value)
@ -38,24 +37,28 @@ export const SubmissionValues: React.FC<Props> = props => {
} catch (e) { } catch (e) {
return row.value return row.value
} }
} },
} },
] ]
return ( return (
<div> <div>
<Descriptions title={t('submission:submission')}> <Descriptions title={t('submission:submission')}>
<Descriptions.Item label={t('submission:country')}>{props.submission.geoLocation.country}</Descriptions.Item> <Descriptions.Item label={t('submission:country')}>
<Descriptions.Item label={t('submission:city')}>{props.submission.geoLocation.city}</Descriptions.Item> {props.submission.geoLocation.country}
<Descriptions.Item label={t('submission:device.type')}>{props.submission.device.type}</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t('submission:device.name')}>{props.submission.device.name}</Descriptions.Item> <Descriptions.Item label={t('submission:city')}>
{props.submission.geoLocation.city}
</Descriptions.Item>
<Descriptions.Item label={t('submission:device.type')}>
{props.submission.device.type}
</Descriptions.Item>
<Descriptions.Item label={t('submission:device.name')}>
{props.submission.device.name}
</Descriptions.Item>
</Descriptions> </Descriptions>
<Table <Table columns={columns} dataSource={props.submission.fields} rowKey={'id'} />
columns={columns}
dataSource={props.submission.fields}
rowKey={'id'}
/>
</div> </div>
) )
} }

View File

@ -1,10 +1,10 @@
import {DatePicker, Form} from 'antd' import { DatePicker, Form } from 'antd'
import moment from 'moment' import 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'
export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => { export const DateType: React.FC<AdminFieldTypeProps> = ({ field }) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -13,19 +13,17 @@ export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => {
label={t('type:date.default')} label={t('type:date.default')}
name={[field.name, 'value']} name={[field.name, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
getValueFromEvent={e => e ? e.format('YYYY-MM-DD') : undefined} getValueFromEvent={(e) => (e ? e.format('YYYY-MM-DD') : undefined)}
getValueProps={e => ({value: e ? moment(e) : undefined})} getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
> >
<DatePicker <DatePicker format={'YYYY-MM-DD'} />
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, 'optionKeys', 'min']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
getValueFromEvent={e => e.format('YYYY-MM-DD')} getValueFromEvent={(e) => e.format('YYYY-MM-DD')}
getValueProps={e => ({value: e ? moment(e) : undefined})} getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
> >
<DatePicker /> <DatePicker />
</Form.Item> </Form.Item>
@ -34,8 +32,8 @@ export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => {
label={t('type:date.max')} label={t('type:date.max')}
name={[field.name, 'optionKeys', 'max']} name={[field.name, 'optionKeys', 'max']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
getValueFromEvent={e => e.format('YYYY-MM-DD')} getValueFromEvent={(e) => e.format('YYYY-MM-DD')}
getValueProps={e => ({value: e ? moment(e) : undefined})} getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
> >
<DatePicker /> <DatePicker />
</Form.Item> </Form.Item>

View File

@ -1,9 +1,9 @@
import {Button, Col, Form, Input, Row} from 'antd' import { Button, Col, Form, Input, Row } from 'antd'
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'
export const DropdownType: React.FC<AdminFieldTypeProps> = props => { export const DropdownType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -16,11 +16,8 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.List <Form.List name={[props.field.name, 'options']}>
name={[props.field.name, 'options']}
>
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
@ -37,7 +34,7 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
<Form.Item <Form.Item
wrapperCol={{ span: 24 }} wrapperCol={{ span: 24 }}
name={[field.name, 'title']} name={[field.name, 'title']}
style={{marginBottom: 0}} style={{ marginBottom: 0 }}
> >
<Input placeholder={t('type:dropdown.titlePlaceholder')} /> <Input placeholder={t('type:dropdown.titlePlaceholder')} />
</Form.Item> </Form.Item>
@ -46,10 +43,8 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
<Form.Item <Form.Item
wrapperCol={{ span: 24 }} wrapperCol={{ span: 24 }}
name={[field.name, 'value']} name={[field.name, 'value']}
style={{marginBottom: 0}} style={{ marginBottom: 0 }}
rules={[ rules={[{ required: true, message: t('validation:valueRequired') }]}
{ required: true, message: t('validation:valueRequired') }
]}
> >
<Input placeholder={t('type:dropdown.valuePlaceholder')} /> <Input placeholder={t('type:dropdown.valuePlaceholder')} />
</Form.Item> </Form.Item>
@ -69,11 +64,9 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
}} }}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Button <Button type={'dashed'} onClick={() => add()}>
type={'dashed'} {t('type:dropdown.addOption')}
onClick={() => add()} </Button>
>
{t('type:dropdown.addOption')}</Button>
</Form.Item> </Form.Item>
</div> </div>
) )

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd' import { Form, Input } from 'antd'
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'
export const EmailType: React.FC<AdminFieldTypeProps> = props => { export const EmailType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -11,9 +11,7 @@ export const EmailType: React.FC<AdminFieldTypeProps> = props => {
<Form.Item <Form.Item
label={t('type:email.default')} label={t('type:email.default')}
name={[props.field.name, 'value']} name={[props.field.name, 'value']}
rules={[ rules={[{ type: 'email', message: t('validation:emailRequired') }]}
{ type: 'email', message: t('validation:emailRequired') }
]}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input type={'email'} /> <Input type={'email'} />

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd' import { Form, Input } from 'antd'
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'
export const HiddenType: React.FC<AdminFieldTypeProps> = props => { export const HiddenType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (

View File

@ -1,29 +1,29 @@
import React from 'react' import React from 'react'
import {DateType} from './date.type' import { DateType } from './date.type'
import {DropdownType} from './dropdown.type' import { DropdownType } from './dropdown.type'
import {EmailType} from './email.type' import { EmailType } from './email.type'
import {HiddenType} from './hidden.type' import { HiddenType } from './hidden.type'
import {LinkType} from './link.type' import { LinkType } from './link.type'
import {NumberType} from './number.type' import { NumberType } from './number.type'
import {RadioType} from './radio.type' import { RadioType } from './radio.type'
import {RatingType} from './rating.type' import { RatingType } from './rating.type'
import {TextType} from './text.type' import { TextType } from './text.type'
import {TextareaType} from './textarea.type' import { TextareaType } from './textarea.type'
import {AdminFieldTypeProps} from './type.props' import { AdminFieldTypeProps } from './type.props'
import {YesNoType} from './yes_no.type' import { YesNoType } from './yes_no.type'
export const adminTypes: { export const adminTypes: {
[key: string]: React.FC<AdminFieldTypeProps> [key: string]: React.FC<AdminFieldTypeProps>
} = { } = {
'textfield': TextType, textfield: TextType,
'date': DateType, date: DateType,
'email': EmailType, email: EmailType,
'textarea': TextareaType, textarea: TextareaType,
'link': LinkType, link: LinkType,
'dropdown': DropdownType, dropdown: DropdownType,
'rating': RatingType, rating: RatingType,
'radio': RadioType, radio: RadioType,
'hidden': HiddenType, hidden: HiddenType,
'yes_no': YesNoType, yes_no: YesNoType,
'number': NumberType, number: NumberType,
} }

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd' import { Form, Input } from 'antd'
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'
export const LinkType: React.FC<AdminFieldTypeProps> = props => { export const LinkType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -11,9 +11,7 @@ export const LinkType: React.FC<AdminFieldTypeProps> = props => {
<Form.Item <Form.Item
label={t('type:link.default')} label={t('type:link.default')}
name={[props.field.name, 'value']} name={[props.field.name, 'value']}
rules={[ rules={[{ type: 'url', message: t('validation:invalidUrl') }]}
{ type: 'url', message: t('validation:invalidUrl') }
]}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input type={'url'} /> <Input type={'url'} />

View File

@ -1,9 +1,9 @@
import {Form, InputNumber} from 'antd' import { Form, InputNumber } from 'antd'
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'
export const NumberType: React.FC<AdminFieldTypeProps> = props => { export const NumberType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (

View File

@ -1,9 +1,9 @@
import {Button, Col, Form, Input, Row} from 'antd' import { Button, Col, Form, Input, Row } from 'antd'
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'
export const RadioType: React.FC<AdminFieldTypeProps> = props => { export const RadioType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
@ -16,11 +16,8 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.List <Form.List name={[props.field.name, 'options']}>
name={[props.field.name, 'options']}
>
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
@ -37,7 +34,7 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
<Form.Item <Form.Item
wrapperCol={{ span: 24 }} wrapperCol={{ span: 24 }}
name={[field.name, 'title']} name={[field.name, 'title']}
style={{marginBottom: 0}} style={{ marginBottom: 0 }}
> >
<Input placeholder={t('type:radio:titlePlaceholder')} /> <Input placeholder={t('type:radio:titlePlaceholder')} />
</Form.Item> </Form.Item>
@ -46,10 +43,8 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
<Form.Item <Form.Item
wrapperCol={{ span: 24 }} wrapperCol={{ span: 24 }}
name={[field.name, 'value']} name={[field.name, 'value']}
style={{marginBottom: 0}} style={{ marginBottom: 0 }}
rules={[ rules={[{ required: true, message: t('validation:valueRequired') }]}
{ required: true, message: t('validation:valueRequired') }
]}
> >
<Input placeholder={t('type:radio:valuePlaceholder')} /> <Input placeholder={t('type:radio:valuePlaceholder')} />
</Form.Item> </Form.Item>
@ -69,10 +64,7 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
}} }}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Button <Button type={'dashed'} onClick={() => add()}>
type={'dashed'}
onClick={() => add()}
>
{t('type:radio:addOption')} {t('type:radio:addOption')}
</Button> </Button>
</Form.Item> </Form.Item>

View File

@ -1,9 +1,9 @@
import {Form, Rate} from 'antd' import { Form, Rate } from 'antd'
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'
export const RatingType: React.FC<AdminFieldTypeProps> = props => { export const RatingType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
// TODO add ratings // TODO add ratings
@ -15,10 +15,7 @@ export const RatingType: React.FC<AdminFieldTypeProps> = props => {
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
extra={t('type:rating.clearNote')} extra={t('type:rating.clearNote')}
> >
<Rate <Rate allowHalf allowClear />
allowHalf
allowClear
/>
</Form.Item> </Form.Item>
</div> </div>
) )

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd' import { Form, Input } from 'antd'
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'
export const TextType: React.FC<AdminFieldTypeProps> = props => { export const TextType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd' import { Form, Input } from 'antd'
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'
export const TextareaType: React.FC<AdminFieldTypeProps> = props => { export const TextareaType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (

View File

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

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd' import { Form, Input } from 'antd'
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'
export const YesNoType: React.FC<AdminFieldTypeProps> = props => { export const YesNoType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
// TODO add switch // TODO add switch

View File

@ -1,24 +1,25 @@
import {Form, message} from 'antd' import { Form, message } from 'antd'
import {useForm} from 'antd/lib/form/Form' import { useForm } from 'antd/lib/form/Form'
import React from 'react' import React from 'react'
import {FormDesignFragment, FormFieldFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment, FormFieldFragment } from '../../graphql/fragment/form.fragment'
import {StyledButton} from '../styled/button' import { StyledButton } from '../styled/button'
import {StyledH1} from '../styled/h1' import { StyledH1 } from '../styled/h1'
import {StyledP} from '../styled/p' import { StyledP } from '../styled/p'
import {fieldTypes} from './types' import { fieldTypes } from './types'
import {TextType} from './types/text.type' import { TextType } from './types/text.type'
import {FieldTypeProps} from './types/type.props' import { FieldTypeProps } from './types/type.props'
interface Props { interface Props {
field: FormFieldFragment field: FormFieldFragment
design: FormDesignFragment design: FormDesignFragment
save: (data: any) => any // eslint-disable-next-line @typescript-eslint/no-explicit-any
next: () => any save: (data: any) => void
prev: () => any next: () => void
prev: () => void
} }
export const Field: React.FC<Props> = ({field, save, design, children, next, prev, ...props}) => { export const Field: React.FC<Props> = ({ field, save, design, next, prev, ...props }) => {
const [form] = useForm() const [form] = useForm()
const FieldInput: React.FC<FieldTypeProps> = fieldTypes[field.type] || TextType const FieldInput: React.FC<FieldTypeProps> = fieldTypes[field.type] || TextType
@ -44,33 +45,42 @@ export const Field: React.FC<Props> = ({field, save, design, children, next, pre
flexDirection: 'column', flexDirection: 'column',
}} }}
> >
<div style={{ <div
flex: 1, style={{
display: 'flex', flex: 1,
flexDirection: 'column', display: 'flex',
padding: 32, flexDirection: 'column',
justifyContent: 'flex-end', padding: 32,
}}> justifyContent: 'flex-end',
<StyledH1 design={design} type={'question'}>{field.title}</StyledH1> }}
{field.description && <StyledP design={design} type={'question'}>{field.description}</StyledP>} >
<StyledH1 design={design} type={'question'}>
{field.title}
</StyledH1>
{field.description && (
<StyledP design={design} type={'question'}>
{field.description}
</StyledP>
)}
<FieldInput <FieldInput design={design} field={field} />
design={design}
field={field}
/>
</div> </div>
<div style={{ <div
padding: 32, style={{
display: 'flex', padding: 32,
}}> display: 'flex',
}}
>
<StyledButton <StyledButton
background={design.colors.buttonColor} background={design.colors.buttonColor}
color={design.colors.buttonTextColor} color={design.colors.buttonTextColor}
highlight={design.colors.buttonActiveColor} highlight={design.colors.buttonActiveColor}
onClick={prev} onClick={prev}
>{'Previous'}</StyledButton> >
{'Previous'}
</StyledButton>
<div style={{flex: 1}} /> <div style={{ flex: 1 }} />
<StyledButton <StyledButton
background={design.colors.buttonColor} background={design.colors.buttonColor}
@ -78,7 +88,9 @@ export const Field: React.FC<Props> = ({field, save, design, children, next, pre
highlight={design.colors.buttonActiveColor} highlight={design.colors.buttonActiveColor}
size={'large'} size={'large'}
onClick={form.submit} onClick={form.submit}
>{'Next'}</StyledButton> >
{'Next'}
</StyledButton>
</div> </div>
</Form> </Form>
) )

View File

@ -1,43 +1,54 @@
import {Space} from 'antd' import { Space } from 'antd'
import React from 'react' import React from 'react'
import {FormDesignFragment, FormPageFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment, FormPageFragment } from '../../graphql/fragment/form.fragment'
import {StyledButton} from '../styled/button' import { StyledButton } from '../styled/button'
import {StyledH1} from '../styled/h1' import { StyledH1 } from '../styled/h1'
import {StyledP} from '../styled/p' import { StyledP } from '../styled/p'
interface Props { interface Props {
type: 'start' | 'end' type: 'start' | 'end'
page: FormPageFragment page: FormPageFragment
design: FormDesignFragment design: FormDesignFragment
next: () => any next: () => void
prev: () => any prev: () => void
} }
export const FormPage: React.FC<Props> = ({page, design, next, prev, type, children, ...props}) => { export const FormPage: React.FC<Props> = ({ page, design, next, ...props }) => {
if (!page.show) { if (!page.show) {
return null return null
} }
return ( return (
<div style={{ <div
display: 'flex', style={{
flexDirection: 'column',
}} {...props}>
<div style={{
flex: 1,
display: 'flex', display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column', flexDirection: 'column',
}}> }}
<StyledH1 design={design} type={'question'}>{page.title}</StyledH1> {...props}
<StyledP design={design} type={'question'}>{page.paragraph}</StyledP> >
<div
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
}}
>
<StyledH1 design={design} type={'question'}>
{page.title}
</StyledH1>
<StyledP design={design} type={'question'}>
{page.paragraph}
</StyledP>
</div> </div>
<div style={{ <div
padding: 32, style={{
display: 'flex', padding: 32,
}}> display: 'flex',
}}
>
{page.buttons.length > 0 && ( {page.buttons.length > 0 && (
<Space> <Space>
{page.buttons.map((button, key) => { {page.buttons.map((button, key) => {
@ -49,13 +60,16 @@ export const FormPage: React.FC<Props> = ({page, design, next, prev, type, child
key={key} key={key}
href={button.url} href={button.url}
target={'_blank'} target={'_blank'}
>{button.text}</StyledButton> rel={'noreferrer'}
>
{button.text}
</StyledButton>
) )
})} })}
</Space> </Space>
)} )}
<div style={{flex: 1}} /> <div style={{ flex: 1 }} />
<StyledButton <StyledButton
background={design.colors.buttonColor} background={design.colors.buttonColor}
@ -63,7 +77,9 @@ export const FormPage: React.FC<Props> = ({page, design, next, prev, type, child
highlight={design.colors.buttonActiveColor} highlight={design.colors.buttonActiveColor}
size={'large'} size={'large'}
onClick={next} onClick={next}
>{page.buttonText || 'Continue'}</StyledButton> >
{page.buttonText || 'Continue'}
</StyledButton>
</div> </div>
</div> </div>
) )

View File

@ -1,16 +1,16 @@
import {Form} from 'antd' import { Form } from 'antd'
import dayjs, {Dayjs} from 'dayjs' import dayjs, { Dayjs } from 'dayjs'
import moment from 'moment' import moment, { Moment } from 'moment'
import React, {useEffect, useState} from 'react' import React, { useEffect, useState } from 'react'
import {StyledDateInput} from '../../styled/date.input' import { StyledDateInput } from '../../styled/date.input'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const DateType: React.FC<FieldTypeProps> = ({ field, design}) => { export const DateType: React.FC<FieldTypeProps> = ({ field, design }) => {
const [min, setMin] = useState<Dayjs>() const [min, setMin] = useState<Dayjs>()
const [max, setMax] = useState<Dayjs>() const [max, setMax] = useState<Dayjs>()
useEffect(() => { useEffect(() => {
field.options.forEach(option => { field.options.forEach((option) => {
if (option.key === 'min') { if (option.key === 'min') {
setMin(dayjs(option.value)) setMin(dayjs(option.value))
} }
@ -24,22 +24,19 @@ export const DateType: React.FC<FieldTypeProps> = ({ field, design}) => {
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[{ required: field.required, message: 'Please provide Information' }]}
{ required: field.required, message: 'Please provide Information' }, getValueFromEvent={(e) => e.format('YYYY-MM-DD')}
]} getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
getValueFromEvent={e => e.format('YYYY-MM-DD')}
getValueProps={e => ({value: e ? moment(e) : undefined})}
initialValue={field.value ? moment(field.value) : undefined} initialValue={field.value ? moment(field.value) : undefined}
> >
<StyledDateInput <StyledDateInput
size={'large'} size={'large'}
design={design} design={design}
autoFocus disabledDate={(d: Moment) => {
disabledDate={(d: any) => { if (min && min.isAfter(d.toDate())) {
if (min && min.isAfter(d)) {
return true return true
} }
if (max && max.isBefore(d)) { if (max && max.isBefore(d.toDate())) {
return true return true
} }
return false return false

View File

@ -1,24 +1,32 @@
import {Form, Select} from 'antd' import { Form, Select } from 'antd'
import React, {useState} from 'react' import React, { useState } from 'react'
import {StyledSelect} from '../../styled/select' import { StyledSelect } from '../../styled/select'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const DropdownType: React.FC<FieldTypeProps> = ({field, design}) => { export const DropdownType: React.FC<FieldTypeProps> = ({ field, design }) => {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
return ( return (
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[{ required: field.required, message: 'Please provide Information' }]}
{ required: field.required, message: 'Please provide Information' },
]}
initialValue={field.value || null} initialValue={field.value || null}
> >
<StyledSelect design={design} open={open} onBlur={() => setOpen(false)} onFocus={() => setOpen(true)} onSelect={() => setOpen(false)}> <StyledSelect
{field.options.filter(option => option.key === null).map(option => ( design={design}
<Select.Option value={option.value} key={option.value}>OK{option.title || option.value}</Select.Option> open={open}
))} onBlur={() => setOpen(false)}
onFocus={() => setOpen(true)}
onSelect={() => setOpen(false)}
>
{field.options
.filter((option) => option.key === null)
.map((option) => (
<Select.Option value={option.value} key={option.value}>
OK{option.title || option.value}
</Select.Option>
))}
</StyledSelect> </StyledSelect>
</Form.Item> </Form.Item>
</div> </div>

View File

@ -1,24 +1,20 @@
import {Form} from 'antd' import { Form } from 'antd'
import React from 'react' import React from 'react'
import {StyledInput} from '../../styled/input' import { StyledInput } from '../../styled/input'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const EmailType: React.FC<FieldTypeProps> = ({field, design}) => { export const EmailType: React.FC<FieldTypeProps> = ({ field, design }) => {
return ( return (
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[
{ required: field.required, message: 'Please provide Information' }, { required: field.required, message: 'Please provide Information' },
{ type: 'email', message: 'Must be a valid email' } { type: 'email', message: 'Must be a valid email' },
]} ]}
initialValue={field.value} initialValue={field.value}
> >
<StyledInput <StyledInput design={design} allowClear size={'large'} />
design={design}
allowClear
size={'large'}
/>
</Form.Item> </Form.Item>
</div> </div>
) )

View File

@ -1,27 +1,27 @@
import React from 'react' import React from 'react'
import {DateType} from './date.type' import { DateType } from './date.type'
import {DropdownType} from './dropdown.type' import { DropdownType } from './dropdown.type'
import {EmailType} from './email.type' import { EmailType } from './email.type'
import {LinkType} from './link.type' import { LinkType } from './link.type'
import {NumberType} from './number.type' import { NumberType } from './number.type'
import {RadioType} from './radio.type' import { RadioType } from './radio.type'
import {RatingType} from './rating.type' import { RatingType } from './rating.type'
import {TextType} from './text.type' import { TextType } from './text.type'
import {TextareaType} from './textarea.type' import { TextareaType } from './textarea.type'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
import {YesNoType} from './yes_no.type' import { YesNoType } from './yes_no.type'
export const fieldTypes: { export const fieldTypes: {
[key: string]: React.FC<FieldTypeProps> [key: string]: React.FC<FieldTypeProps>
} = { } = {
'textfield': TextType, textfield: TextType,
'date': DateType, date: DateType,
'email': EmailType, email: EmailType,
'textarea': TextareaType, textarea: TextareaType,
'link': LinkType, link: LinkType,
'dropdown': DropdownType, dropdown: DropdownType,
'rating': RatingType, rating: RatingType,
'radio': RadioType, radio: RadioType,
'yes_no': YesNoType, yes_no: YesNoType,
'number': NumberType, number: NumberType,
} }

View File

@ -1,24 +1,20 @@
import {Form} from 'antd' import { Form } from 'antd'
import React from 'react' import React from 'react'
import {StyledInput} from '../../styled/input' import { StyledInput } from '../../styled/input'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const LinkType: React.FC<FieldTypeProps> = ({field, design}) => { export const LinkType: React.FC<FieldTypeProps> = ({ field, design }) => {
return ( return (
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[
{ required: field.required, message: 'Please provide Information' }, { required: field.required, message: 'Please provide Information' },
{ type: 'url', message: 'Must be a valid URL' } { type: 'url', message: 'Must be a valid URL' },
]} ]}
initialValue={field.value} initialValue={field.value}
> >
<StyledInput <StyledInput design={design} allowClear size={'large'} />
design={design}
allowClear
size={'large'}
/>
</Form.Item> </Form.Item>
</div> </div>
) )

View File

@ -1,9 +1,9 @@
import {Form} from 'antd' import { Form } from 'antd'
import React from 'react' import React from 'react'
import {StyledNumberInput} from '../../styled/number.input' import { StyledNumberInput } from '../../styled/number.input'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const NumberType: React.FC<FieldTypeProps> = ({field, design}) => { export const NumberType: React.FC<FieldTypeProps> = ({ field, design }) => {
return ( return (
<div> <div>
<Form.Item <Form.Item
@ -14,10 +14,7 @@ export const NumberType: React.FC<FieldTypeProps> = ({field, design}) => {
]} ]}
initialValue={parseFloat(field.value)} initialValue={parseFloat(field.value)}
> >
<StyledNumberInput <StyledNumberInput design={design} size={'large'} />
design={design}
size={'large'}
/>
</Form.Item> </Form.Item>
</div> </div>
) )

View File

@ -1,28 +1,26 @@
import {Form, Radio} from 'antd' import { Form, Radio } from 'antd'
import React from 'react' import React from 'react'
import {StyledRadio} from '../../styled/radio' import { StyledRadio } from '../../styled/radio'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const RadioType: React.FC<FieldTypeProps> = ({field, design}) => { export const RadioType: React.FC<FieldTypeProps> = ({ field, design }) => {
return ( return (
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[{ required: field.required, message: 'Please provide Information' }]}
{ required: field.required, message: 'Please provide Information' }, initialValue={field.options
]} .map((option) => option.value)
initialValue={field.options.map(option => option.value).find(value => value === field.value)} .find((value) => value === field.value)}
> >
<Radio.Group> <Radio.Group>
{field.options.filter(option => option.key === null).map(option => ( {field.options
<StyledRadio .filter((option) => option.key === null)
design={design} .map((option) => (
value={option.value} <StyledRadio design={design} value={option.value} key={option.value}>
key={option.value} {option.title || option.value}
> </StyledRadio>
{option.title || option.value} ))}
</StyledRadio>
))}
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
</div> </div>

View File

@ -1,17 +1,15 @@
import {Form, Rate} from 'antd' import { Form, Rate } from 'antd'
import React from 'react' import React from 'react'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const RatingType: React.FC<FieldTypeProps> = ({field}) => { export const RatingType: React.FC<FieldTypeProps> = ({ field }) => {
// TODO add ratings // TODO add ratings
return ( return (
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[{ required: field.required, message: 'Please provide Information' }]}
{ required: field.required, message: 'Please provide Information' },
]}
initialValue={parseFloat(field.value)} initialValue={parseFloat(field.value)}
> >
<Rate allowHalf /> <Rate allowHalf />

View File

@ -1,27 +1,19 @@
import {Form} from 'antd' import { Form } from 'antd'
import React from 'react' import React from 'react'
import {StyledInput} from '../../styled/input' import { StyledInput } from '../../styled/input'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const TextType: React.FC<FieldTypeProps> = ({field, design}) => { export const TextType: React.FC<FieldTypeProps> = ({ field, design }) => {
// TODO focus when becomes visible // TODO focus when becomes visible
return ( return (
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[{ required: field.required, message: 'Please provide Information' }]}
{ required: field.required, message: 'Please provide Information' }
]}
initialValue={field.value} initialValue={field.value}
> >
<StyledInput <StyledInput design={design} allowClear size={'large'} />
design={design}
allowClear
size={'large'}
/>
</Form.Item> </Form.Item>
</div> </div>
) )

View File

@ -1,23 +1,17 @@
import {Form} from 'antd' import { Form } from 'antd'
import React from 'react' import React from 'react'
import {StyledTextareaInput} from '../../styled/textarea.input' import { StyledTextareaInput } from '../../styled/textarea.input'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const TextareaType: React.FC<FieldTypeProps> = ({field, design}) => { export const TextareaType: React.FC<FieldTypeProps> = ({ field, design }) => {
return ( return (
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[{ required: field.required, message: 'Please provide Information' }]}
{ required: field.required, message: 'Please provide Information' },
]}
initialValue={field.value} initialValue={field.value}
> >
<StyledTextareaInput <StyledTextareaInput design={design} allowClear autoSize />
design={design}
allowClear
autoSize
/>
</Form.Item> </Form.Item>
</div> </div>
) )

View File

@ -1,4 +1,4 @@
import {FormDesignFragment, FormFieldFragment} from '../../../graphql/fragment/form.fragment' import { FormDesignFragment, FormFieldFragment } from '../../../graphql/fragment/form.fragment'
export interface FieldTypeProps { export interface FieldTypeProps {
field: FormFieldFragment field: FormFieldFragment

View File

@ -1,15 +1,13 @@
import {Form, Switch} from 'antd' import { Form, Switch } from 'antd'
import React from 'react' import React from 'react'
import {FieldTypeProps} from './type.props' import { FieldTypeProps } from './type.props'
export const YesNoType: React.FC<FieldTypeProps> = ({field}) => { export const YesNoType: React.FC<FieldTypeProps> = ({ field }) => {
return ( return (
<div> <div>
<Form.Item <Form.Item
name={[field.id, 'value']} name={[field.id, 'value']}
rules={[ rules={[{ required: field.required, message: 'Please provide Information' }]}
{ required: field.required, message: 'Please provide Information' },
]}
initialValue={field.value} initialValue={field.value}
> >
<Switch /> <Switch />

View File

@ -1,12 +1,12 @@
import React, {useEffect} from 'react' import React, { useEffect } from 'react'
import {BlockPicker} from 'react-color' import { BlockPicker } from 'react-color'
interface Props { interface Props {
value?: string value?: string
onChange?: any onChange?: (value: string) => void
} }
export const InputColor: React.FC<Props> = props => { export const InputColor: React.FC<Props> = (props) => {
useEffect(() => { useEffect(() => {
if (!props.value) { if (!props.value) {
props.onChange('#FFF') props.onChange('#FFF')
@ -18,13 +18,13 @@ 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) => props.onChange(e.hex)}
styles={{ styles={{
default: { default: {
card: { card: {
flexDirection: 'row', flexDirection: 'row',
display: 'flex', display: 'flex',
boxShadow: 'none' boxShadow: 'none',
}, },
head: { head: {
flex: 1, flex: 1,

View File

@ -1,20 +1,22 @@
import {Spin} from 'antd' import { Spin } from 'antd'
import React from 'react' import React from 'react'
interface Props { interface Props {
message?: string message?: string
} }
export const LoadingPage: React.FC<Props> = props => { export const LoadingPage: React.FC<Props> = (props) => {
return ( return (
<div style={{ <div
height: '100vh', style={{
justifyContent: 'center', height: '100vh',
alignItems: 'center', justifyContent: 'center',
display: 'flex', alignItems: 'center',
flexDirection: 'column', display: 'flex',
}}> flexDirection: 'column',
<Spin size="large"/> }}
>
<Spin size="large" />
{props.message} {props.message}
</div> </div>
) )

View File

@ -1,5 +1,5 @@
import {HomeOutlined, MessageOutlined, TeamOutlined} from '@ant-design/icons' import { HomeOutlined, MessageOutlined, TeamOutlined } from '@ant-design/icons'
import {UserOutlined} from '@ant-design/icons/lib' import { UserOutlined } from '@ant-design/icons/lib'
import React from 'react' import React from 'react'
export interface SideMenuElement { export interface SideMenuElement {
@ -9,7 +9,7 @@ export interface SideMenuElement {
name: string name: string
group?: boolean group?: boolean
href?: string href?: string
icon?: any icon?: JSX.Element
} }
export const sideMenu: SideMenuElement[] = [ export const sideMenu: SideMenuElement[] = [
@ -36,7 +36,7 @@ export const sideMenu: SideMenuElement[] = [
href: '/admin/forms', href: '/admin/forms',
icon: <MessageOutlined />, icon: <MessageOutlined />,
}, },
] ],
}, },
{ {
key: 'administration', key: 'administration',

View File

@ -1,14 +1,14 @@
import {CaretDownOutlined, UserOutlined} from '@ant-design/icons' import { CaretDownOutlined, UserOutlined } from '@ant-design/icons'
import {MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons/lib' import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons/lib'
import {Dropdown, Layout, Menu, PageHeader, Select, Spin, Tag} from 'antd' import { Dropdown, Layout, Menu, PageHeader, Select, Spin, Tag } from 'antd'
import Link from 'next/link' import Link from 'next/link'
import {useRouter} from 'next/router' import { useRouter } from 'next/router'
import React, {FunctionComponent} from 'react' import React, { CSSProperties, FunctionComponent } from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {languages} from '../i18n' import { languages } from '../i18n'
import {sideMenu, SideMenuElement} from './sidemenu' import { sideMenu, SideMenuElement } from './sidemenu'
import {useWindowSize} from './use.window.size' import { useWindowSize } from './use.window.size'
import {clearAuth} from './with.auth' import { clearAuth } from './with.auth'
const { SubMenu, ItemGroup } = Menu const { SubMenu, ItemGroup } = Menu
const { Header, Content, Sider } = Layout const { Header, Content, Sider } = Layout
@ -22,15 +22,14 @@ interface BreadcrumbEntry {
interface Props { interface Props {
loading?: boolean loading?: boolean
padded?: boolean padded?: boolean
style?: any style?: CSSProperties
selected?: string selected?: string
breadcrumbs?: BreadcrumbEntry[] breadcrumbs?: BreadcrumbEntry[]
title?: string title?: string
subTitle?: string subTitle?: string
extra?: any[] extra?: JSX.Element[]
} }
const Structure: FunctionComponent<Props> = (props) => { const Structure: FunctionComponent<Props> = (props) => {
@ -63,58 +62,62 @@ const Structure: FunctionComponent<Props> = (props) => {
}, [props.selected]) }, [props.selected])
const buildMenu = (data: SideMenuElement[]): JSX.Element[] => { const buildMenu = (data: SideMenuElement[]): JSX.Element[] => {
return data.map((element): JSX.Element => { return data.map(
if (element.items && element.items.length > 0) { (element): JSX.Element => {
if (element.group) { if (element.items && element.items.length > 0) {
if (element.group) {
return (
<ItemGroup
key={element.key}
title={
<div
style={{
textTransform: 'uppercase',
paddingTop: 16,
fontWeight: 'bold',
color: '#444',
}}
>
{element.icon}
{element.name}
</div>
}
>
{buildMenu(element.items)}
</ItemGroup>
)
}
return ( return (
<ItemGroup <SubMenu
key={element.key} key={element.key}
title={( title={
<div style={{ <span>
textTransform: 'uppercase',
paddingTop: 16,
fontWeight: 'bold',
color: '#444'
}}>
{element.icon} {element.icon}
{element.name} {element.name}
</div> </span>
)} }
> >
{buildMenu(element.items)} {buildMenu(element.items)}
</ItemGroup> </SubMenu>
) )
} }
return ( return (
<SubMenu <Menu.Item
onClick={(): void => {
if (element.href) {
router.push(element.href)
}
}}
key={element.key} key={element.key}
title={
<span>
{element.icon}
{element.name}
</span>
}
> >
{buildMenu(element.items)} {element.icon}
</SubMenu> {element.name}
</Menu.Item>
) )
} }
)
return (
<Menu.Item
onClick={(): void => {
if (element.href) {
router.push(element.href)
}
}}
key={element.key}
>
{element.icon}
{element.name}
</Menu.Item>
)
})
} }
const signOut = async (): Promise<void> => { const signOut = async (): Promise<void> => {
@ -129,46 +132,57 @@ const Structure: FunctionComponent<Props> = (props) => {
paddingLeft: 0, paddingLeft: 0,
}} }}
> >
<div style={{ <div
float: 'left', style={{
color: '#FFF', float: 'left',
fontSize: 14, color: '#FFF',
marginRight: 26, fontSize: 14,
fontWeight: 'bold' marginRight: 26,
}}> fontWeight: 'bold',
}}
>
{React.createElement(sidebar ? MenuUnfoldOutlined : MenuFoldOutlined, { {React.createElement(sidebar ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'sidebar-toggle', className: 'sidebar-toggle',
onClick: () => setSidebar(!sidebar), onClick: () => setSidebar(!sidebar),
})} })}
<img src={require('assets/images/logo_white_small.png')} height={30} style={{marginRight: 16}} alt={'OhMyForm'} /> <img
src={require('assets/images/logo_white_small.png')}
height={30}
style={{ marginRight: 16 }}
alt={'OhMyForm'}
/>
</div> </div>
<div style={{float: 'right', display: 'flex', height: '100%'}}> <div style={{ float: 'right', display: 'flex', height: '100%' }}>
<Dropdown <Dropdown
overlay={( overlay={
<Menu> <Menu>
<Menu.Item onClick={() => router.push('/admin/profile')}>Profile</Menu.Item> <Menu.Item onClick={() => router.push('/admin/profile')}>Profile</Menu.Item>
<Menu.Divider/> <Menu.Divider />
<Menu.Item onClick={signOut}>Logout</Menu.Item> <Menu.Item onClick={signOut}>Logout</Menu.Item>
</Menu> </Menu>
)} }
onVisibleChange={setUserMenu} onVisibleChange={setUserMenu}
visible={userMenu} visible={userMenu}
> >
<a style={{ <div
color: '#FFF', style={{
alignItems: 'center', color: '#FFF',
display: 'inline-flex', alignItems: 'center',
}}> display: 'inline-flex',
<UserOutlined style={{fontSize: 24}} /> }}
>
<UserOutlined style={{ fontSize: 24 }} />
<CaretDownOutlined /> <CaretDownOutlined />
</a> </div>
</Dropdown> </Dropdown>
</div> </div>
</Header> </Header>
<Layout style={{ <Layout
height: '100%', style={{
}}> height: '100%',
}}
>
<Sider <Sider
collapsed={sidebar} collapsed={sidebar}
trigger={null} trigger={null}
@ -193,20 +207,21 @@ const Structure: FunctionComponent<Props> = (props) => {
> >
{buildMenu(sideMenu)} {buildMenu(sideMenu)}
</Menu> </Menu>
<Menu <Menu mode="inline" selectable={false}>
mode="inline"
selectable={false}
>
<Menu.Item className={'language-selector'}> <Menu.Item className={'language-selector'}>
<Select <Select
bordered={false} bordered={false}
value={i18n.language.replace(/-.*/, '')} value={i18n.language.replace(/-.*/, '')}
onChange={next => i18n.changeLanguage(next)} onChange={(next) => i18n.changeLanguage(next)}
style={{ style={{
width: '100%', width: '100%',
}} }}
> >
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )} {languages.map((language) => (
<Select.Option value={language} key={language}>
{t(`language:${language}`)}
</Select.Option>
))}
</Select> </Select>
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>
@ -222,17 +237,17 @@ const Structure: FunctionComponent<Props> = (props) => {
extra={props.extra} extra={props.extra}
breadcrumb={{ breadcrumb={{
routes: [ routes: [
...(props.breadcrumbs || []).map(b => ({ ...(props.breadcrumbs || []).map((b) => ({
breadcrumbName: b.name, breadcrumbName: b.name,
path: '' path: '',
})), })),
{ {
breadcrumbName: props.title, breadcrumbName: props.title,
path: '' path: '',
} },
], ],
params: props.breadcrumbs, params: props.breadcrumbs,
itemRender: (route, params: BreadcrumbEntry[], routes, paths) => { itemRender(route, params: BreadcrumbEntry[], routes) {
if (routes.indexOf(route) === routes.length - 1) { if (routes.indexOf(route) === routes.length - 1) {
return <span>{route.breadcrumbName}</span> return <span>{route.breadcrumbName}</span>
} }
@ -240,27 +255,22 @@ const Structure: FunctionComponent<Props> = (props) => {
const entry = params[routes.indexOf(route)] const entry = params[routes.indexOf(route)]
return ( return (
<Link <Link href={entry.href} as={entry.as || entry.href}>
href={entry.href} {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
as={entry.as || entry.href} <a>{entry.name}</a>
>
<a>
{entry.name}
</a>
</Link> </Link>
) )
}}} },
}}
/> />
)} )}
<Spin <Spin spinning={!!props.loading}>
spinning={!!props.loading}
>
<Content <Content
style={{ style={{
background: props.padded ? '#fff' : null, background: props.padded ? '#fff' : null,
padding: props.padded ? 24 : 0, padding: props.padded ? 24 : 0,
...props.style ...props.style,
}} }}
> >
{props.children} {props.children}

View File

@ -1,29 +1,27 @@
import {Button} from 'antd' import { Button } from 'antd'
import {ButtonProps} from 'antd/lib/button/button' import { ButtonProps } from 'antd/lib/button/button'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {darken, lighten} from './color.change' import { darken, lighten } from './color.change'
interface Props extends ButtonProps { interface Props extends ButtonProps {
background: any background: string
highlight: any highlight: string
color: any color: string
} }
const Styled = styled(Button)` const Styled = styled(Button)`
background: ${props => props.background}; background: ${(props) => props.background};
color: ${props => props.color}; color: ${(props) => props.color};
border-color: ${props => darken(props.background, 10)}; border-color: ${(props) => darken(props.background, 10)};
:hover { :hover {
color: ${props => props.highlight}; color: ${(props) => props.highlight};
background-color: ${props => lighten(props.background, 10)}; background-color: ${(props) => lighten(props.background, 10)};
border-color: ${props => darken(props.highlight, 10)}; border-color: ${(props) => darken(props.highlight, 10)};
} }
` `
export const StyledButton: React.FC<Props> = ({children, ...props}) => { export const StyledButton: React.FC<Props> = ({ children, ...props }) => {
return ( return <Styled {...props}>{children}</Styled>
<Styled {...props}>{children}</Styled>
)
} }

View File

@ -3,58 +3,58 @@
* *
* @author Chris Coyier * @author Chris Coyier
*/ */
function LightenDarkenColor(col, amt) { function LightenDarkenColor(col, amt): string {
let usePound = false; let usePound = false
if (col[0] == "#") { if (col[0] == '#') {
col = col.slice(1); col = col.slice(1)
usePound = true; usePound = true
} }
const num = parseInt(col, 16) const num = parseInt(col, 16)
let r = (num >> 16) + amt; let r = (num >> 16) + amt
if (r > 255) r = 255; if (r > 255) r = 255
else if (r < 0) r = 0; else if (r < 0) r = 0
let b = ((num >> 8) & 0x00FF) + amt; let b = ((num >> 8) & 0x00ff) + amt
if (b > 255) b = 255; if (b > 255) b = 255
else if (b < 0) b = 0; else if (b < 0) b = 0
let g = (num & 0x0000FF) + amt; let g = (num & 0x0000ff) + amt
if (g > 255) g = 255; if (g > 255) g = 255
else if (g < 0) g = 0; else if (g < 0) g = 0
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16); return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16)
} }
export const transparentize = (col: string, amt: number) => { export const transparentize = (col: string, amt: number): string => {
if (col[0] == "#") { if (col[0] == '#') {
col = col.slice(1); col = col.slice(1)
} }
const num = parseInt(col, 16) const num = parseInt(col, 16)
let r = (num >> 16) + amt; let r = (num >> 16) + amt
if (r > 255) r = 255; if (r > 255) r = 255
else if (r < 0) r = 0; else if (r < 0) r = 0
let b = ((num >> 8) & 0x00FF) + amt; let b = ((num >> 8) & 0x00ff) + amt
if (b > 255) b = 255; if (b > 255) b = 255
else if (b < 0) b = 0; else if (b < 0) b = 0
let g = (num & 0x0000FF) + amt; let g = (num & 0x0000ff) + amt
if (g > 255) g = 255; if (g > 255) g = 255
else if (g < 0) g = 0; else if (g < 0) g = 0
return `rgba(${r}, ${b}, ${g}, ${1 - amt / 100})` return `rgba(${r}, ${b}, ${g}, ${1 - amt / 100})`
} }
export const lighten = (color: string, amount: number) => LightenDarkenColor(color, amount) export const lighten = (color: string, amount: number): string => LightenDarkenColor(color, amount)
export const darken = (color: string, amount: number) => LightenDarkenColor(color, -amount) export const darken = (color: string, amount: number): string => LightenDarkenColor(color, -amount)

View File

@ -1,51 +1,49 @@
import {DatePicker} from 'antd' import { DatePicker } from 'antd'
import {PickerProps} from 'antd/lib/date-picker/generatePicker' import { PickerProps } from 'antd/lib/date-picker/generatePicker'
import {Moment} from 'moment' import { Moment } from 'moment'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import {transparentize} from './color.change' import { transparentize } from './color.change'
type Props = { design: FormDesignFragment } & PickerProps<Moment> type Props = { design: FormDesignFragment } & PickerProps<Moment>
const Field = styled(DatePicker)` const Field = styled(DatePicker)`
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
background: none !important; background: none !important;
border-right: none; border-right: none;
border-top: none; border-top: none;
border-left: none; border-left: none;
border-radius: 0; border-radius: 0;
width: 100%; width: 100%;
:hover, :hover,
:active { :active {
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
} }
&.ant-picker { &.ant-picker {
box-shadow: none box-shadow: none;
} }
.ant-picker-clear { .ant-picker-clear {
background: none; background: none;
} }
input { input {
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${props => transparentize(props.design.colors.answerColor, 60)} color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
} }
` `
export const StyledDateInput: React.FC<Props> = ({children, ...props}) => { export const StyledDateInput: React.FC<Props> = ({ children, ...props }) => {
return ( return <Field {...props}>{children}</Field>
<Field {...props}>{children}</Field>
)
} }

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
interface Props { interface Props {
type: 'question' | 'answer' type: 'question' | 'answer'
@ -8,11 +8,12 @@ interface Props {
} }
const Header = styled.h1` const Header = styled.h1`
color: ${props => props.type === 'question' ? props.design.colors.questionColor : props.design.colors.answerColor} color: ${(props) =>
props.type === 'question'
? props.design.colors.questionColor
: props.design.colors.answerColor};
` `
export const StyledH1: React.FC<Props> = ({children, ...props}) => { export const StyledH1: React.FC<Props> = ({ children, ...props }) => {
return ( return <Header {...props}>{children}</Header>
<Header {...props}>{children}</Header>
)
} }

View File

@ -1,17 +1,18 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
interface Props { interface Props {
type: 'question' | 'answer' type: 'question' | 'answer'
design: FormDesignFragment design: FormDesignFragment
} }
const Header = styled.h2` const Header = styled.h2`
color: ${props => props.type === 'question' ? props.design.colors.questionColor : props.design.colors.answerColor} color: ${(props) =>
props.type === 'question'
? props.design.colors.questionColor
: props.design.colors.answerColor};
` `
export const StyledH2: React.FC<Props> = ({children, ...props}) => { export const StyledH2: React.FC<Props> = ({ children, ...props }) => {
return ( return <Header {...props}>{children}</Header>
<Header {...props}>{children}</Header>
)
} }

View File

@ -1,52 +1,50 @@
import {Input} from 'antd' import { Input } from 'antd'
import {InputProps} from 'antd/lib/input/Input' import { InputProps } from 'antd/lib/input/Input'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import {transparentize} from './color.change' import { transparentize } from './color.change'
interface Props extends InputProps{ interface Props extends InputProps {
design: FormDesignFragment design: FormDesignFragment
} }
const Field = styled(Input)` const Field = styled(Input)`
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
background: none !important; background: none !important;
border-right: none; border-right: none;
border-top: none; border-top: none;
border-left: none; border-left: none;
border-radius: 0; border-radius: 0;
:focus { :focus {
outline: ${props => props.design.colors.answerColor} auto 5px outline: ${(props) => props.design.colors.answerColor} auto 5px;
} }
:hover, :hover,
:active { :active {
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
} }
&.ant-input-affix-wrapper { &.ant-input-affix-wrapper {
box-shadow: none box-shadow: none;
} }
input { input {
background: none !important; background: none !important;
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${props => transparentize(props.design.colors.answerColor, 60)} color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
} }
` `
export const StyledInput: React.FC<Props> = ({children, ...props}) => { export const StyledInput: React.FC<Props> = ({ children, ...props }) => {
return ( return <Field {...props}>{children}</Field>
<Field {...props}>{children}</Field>
)
} }

View File

@ -1,53 +1,51 @@
import {InputNumber} from 'antd' import { InputNumber } from 'antd'
import {InputNumberProps} from 'antd/lib/input-number' import { InputNumberProps } from 'antd/lib/input-number'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import {transparentize} from './color.change' import { transparentize } from './color.change'
interface Props extends InputNumberProps { interface Props extends InputNumberProps {
design: FormDesignFragment design: FormDesignFragment
} }
const Field = styled(InputNumber)` const Field = styled(InputNumber)`
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
background: none !important; background: none !important;
border-right: none; border-right: none;
border-top: none; border-top: none;
border-left: none; border-left: none;
border-radius: 0; border-radius: 0;
width: 100%; width: 100%;
:focus { :focus {
outline: ${props => props.design.colors.answerColor} auto 5px outline: ${(props) => props.design.colors.answerColor} auto 5px;
} }
:hover, :hover,
:active { :active {
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
} }
&.ant-input-number { &.ant-input-number {
box-shadow: none box-shadow: none;
} }
input { input {
background: none !important; background: none !important;
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${props => transparentize(props.design.colors.answerColor, 60)} color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
} }
` `
export const StyledNumberInput: React.FC<Props> = ({children, ...props}) => { export const StyledNumberInput: React.FC<Props> = ({ children, ...props }) => {
return ( return <Field {...props}>{children}</Field>
<Field {...props}>{children}</Field>
)
} }

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
interface Props { interface Props {
type: 'question' | 'answer' type: 'question' | 'answer'
@ -8,11 +8,12 @@ interface Props {
} }
const Paragraph = styled.p` const Paragraph = styled.p`
color: ${props => props.type === 'question' ? props.design.colors.questionColor : props.design.colors.answerColor} color: ${(props) =>
props.type === 'question'
? props.design.colors.questionColor
: props.design.colors.answerColor};
` `
export const StyledP: React.FC<Props> = ({children, ...props}) => { export const StyledP: React.FC<Props> = ({ children, ...props }) => {
return ( return <Paragraph {...props}>{children}</Paragraph>
<Paragraph {...props}>{children}</Paragraph>
)
} }

View File

@ -1,39 +1,37 @@
import {Radio} from 'antd' import { Radio } from 'antd'
import {RadioProps} from 'antd/lib/radio/interface' import { RadioProps } from 'antd/lib/radio/interface'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
interface Props extends RadioProps { interface Props extends RadioProps {
design: FormDesignFragment design: FormDesignFragment
} }
const Field = styled(Radio)` const Field = styled(Radio)`
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
border-color: ${props => props.design.colors.answerColor}; border-color: ${(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.design.colors.answerColor};
&::after { &::after {
background: ${props => props.design.colors.answerColor}; background: ${(props) => props.design.colors.answerColor};
} }
} }
&::after { &::after {
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
} }
} }
.anticon { .anticon {
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
} }
` `
export const StyledRadio: React.FC<Props> = ({children, ...props}) => { export const StyledRadio: React.FC<Props> = ({ children, ...props }) => {
return ( return <Field {...props}>{children}</Field>
<Field {...props}>{children}</Field>
)
} }

View File

@ -1,9 +1,9 @@
import {Select} from 'antd' import { Select } from 'antd'
import {SelectProps} from 'antd/lib/select' import { SelectProps } from 'antd/lib/select'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import {transparentize} from './color.change' import { transparentize } from './color.change'
interface Props extends SelectProps<string> { interface Props extends SelectProps<string> {
design: FormDesignFragment design: FormDesignFragment
@ -11,8 +11,8 @@ interface Props extends SelectProps<string> {
const Field = styled(Select)` const Field = styled(Select)`
.ant-select-selector { .ant-select-selector {
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
border-color: ${props => props.design.colors.answerColor} !important; border-color: ${(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;
@ -20,32 +20,30 @@ const Field = styled(Select)`
border-radius: 0 !important; border-radius: 0 !important;
box-shadow: none !important; box-shadow: none !important;
} }
:focus { :focus {
outline: ${props => props.design.colors.answerColor} auto 5px outline: ${(props) => props.design.colors.answerColor} auto 5px;
} }
:hover, :hover,
:active { :active {
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
} }
input { input {
background: none !important; background: none !important;
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${props => transparentize(props.design.colors.answerColor, 60)} color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
} }
` `
export const StyledSelect: React.FC<Props> = ({children, ...props}) => { export const StyledSelect: React.FC<Props> = ({ children, ...props }) => {
return ( return <Field {...props}>{children}</Field>
<Field {...props}>{children}</Field>
)
} }

View File

@ -1,50 +1,48 @@
import {Input} from 'antd' import { Input } from 'antd'
import {TextAreaProps} from 'antd/lib/input/TextArea' import { TextAreaProps } from 'antd/lib/input/TextArea'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment' import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import {transparentize} from './color.change' import { transparentize } from './color.change'
interface Props extends TextAreaProps { interface Props extends TextAreaProps {
design: FormDesignFragment design: FormDesignFragment
} }
const Field = styled(Input.TextArea)` const Field = styled(Input.TextArea)`
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
background: none !important; background: none !important;
border-right: none; border-right: none;
border-top: none; border-top: none;
border-left: none; border-left: none;
border-radius: 0; border-radius: 0;
:focus { :focus {
outline: none; outline: none;
box-shadow: none; box-shadow: none;
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
} }
:hover, :hover,
:active { :active {
border-color: ${props => props.design.colors.answerColor}; border-color: ${(props) => props.design.colors.answerColor};
} }
input { input {
background: none !important; background: none !important;
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
::placeholder { ::placeholder {
color: ${props => transparentize(props.design.colors.answerColor, 60)} color: ${(props) => transparentize(props.design.colors.answerColor, 60)};
} }
} }
.anticon { .anticon {
color: ${props => props.design.colors.answerColor}; color: ${(props) => props.design.colors.answerColor};
} }
` `
export const StyledTextareaInput: React.FC<Props> = ({children, ...props}) => { export const StyledTextareaInput: React.FC<Props> = ({ children, ...props }) => {
return ( return <Field {...props}>{children}</Field>
<Field {...props}>{children}</Field>
)
} }

View File

@ -1,4 +1,4 @@
import {Tooltip} from 'antd' import { Tooltip } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import React from 'react' import React from 'react'
@ -9,15 +9,17 @@ interface Props {
date: string date: string
} }
export const TimeAgo: React.FC<Props> = props => { export const TimeAgo: React.FC<Props> = (props) => {
const date = dayjs(props.date) const date = dayjs(props.date)
return ( return (
<Tooltip title={date.format('YYYY-MM-DD HH:mm:ss')}> <Tooltip title={date.format('YYYY-MM-DD HH:mm:ss')}>
<div style={{ <div
display: 'inline-block' style={{
}}> display: 'inline-block',
}}
>
{date.fromNow()} {date.fromNow()}
</div> </div>
</Tooltip> </Tooltip>
) )
} }

View File

@ -1,37 +1,47 @@
import {useMutation} from '@apollo/react-hooks' import { useMutation } from '@apollo/react-hooks'
import {useCallback, useEffect, useState} from 'react' import { useCallback, useEffect, useState } from 'react'
import { import {
SUBMISSION_SET_FIELD_MUTATION, SUBMISSION_SET_FIELD_MUTATION,
SubmissionSetFieldMutationData, SubmissionSetFieldMutationData,
SubmissionSetFieldMutationVariables SubmissionSetFieldMutationVariables,
} from '../graphql/mutation/submission.set.field.mutation' } from '../graphql/mutation/submission.set.field.mutation'
import { import {
SUBMISSION_START_MUTATION, SUBMISSION_START_MUTATION,
SubmissionStartMutationData, SubmissionStartMutationData,
SubmissionStartMutationVariables SubmissionStartMutationVariables,
} from '../graphql/mutation/submission.start.mutation' } from '../graphql/mutation/submission.start.mutation'
export const useSubmission = (formId: string) => { interface Submission {
const [submission, setSubmission] = useState<{ id: string, token: string }>() // eslint-disable-next-line @typescript-eslint/no-explicit-any
setField: (fieldId: string, data: any) => Promise<void>
finish: () => void
}
const [start] = useMutation<SubmissionStartMutationData, SubmissionStartMutationVariables>(SUBMISSION_START_MUTATION) export const useSubmission = (formId: string): Submission => {
const [save] = useMutation<SubmissionSetFieldMutationData, SubmissionSetFieldMutationVariables>(SUBMISSION_SET_FIELD_MUTATION) const [submission, setSubmission] = useState<{ id: string; token: string }>()
const [start] = useMutation<SubmissionStartMutationData, SubmissionStartMutationVariables>(
SUBMISSION_START_MUTATION
)
const [save] = useMutation<SubmissionSetFieldMutationData, SubmissionSetFieldMutationVariables>(
SUBMISSION_SET_FIELD_MUTATION
)
useEffect(() => { useEffect(() => {
(async () => { ;(async () => {
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({ const { data } = await 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,
} },
} },
} },
}) })
setSubmission({ setSubmission({
@ -41,19 +51,23 @@ export const useSubmission = (formId: string) => {
})() })()
}, [formId]) }, [formId])
const setField = useCallback(async (fieldId: string, data: any) => { const setField = useCallback(
console.log('just save', fieldId, data) // eslint-disable-next-line @typescript-eslint/no-explicit-any
await save({ async (fieldId: string, data: any) => {
variables: { console.log('just save', fieldId, data)
submission: submission.id, await save({
field: { variables: {
token: submission.token, submission: submission.id,
field: fieldId, field: {
data: JSON.stringify(data) token: submission.token,
} field: fieldId,
} data: JSON.stringify(data),
}) },
}, [submission]) },
})
},
[submission]
)
const finish = useCallback(() => { const finish = useCallback(() => {
console.log('finish submission!!', formId) console.log('finish submission!!', formId)

View File

@ -1,29 +1,29 @@
import {useEffect, useState} from 'react' import { useEffect, useState } from 'react'
export const useWindowSize = () => { export const useWindowSize = (): { width?: number; height?: number } => {
const isClient = typeof window === 'object'; const isClient = typeof window === 'object'
function getSize() { function getSize() {
return { return {
width: isClient ? window.innerWidth : undefined, width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined height: isClient ? window.innerHeight : undefined,
}; }
} }
const [windowSize, setWindowSize] = useState(getSize); const [windowSize, setWindowSize] = useState(getSize)
useEffect(() => { useEffect(() => {
if (!isClient) { if (!isClient) {
return; return
} }
function handleResize() { function handleResize() {
setWindowSize(getSize()); setWindowSize(getSize())
} }
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize)
}, []); // Empty array ensures that effect is only run on mount and unmount }, []) // Empty array ensures that effect is only run on mount and unmount
return windowSize; return windowSize
} }

View File

@ -1,9 +1,9 @@
import {Form, Input, Select, Tabs} from 'antd' import { Form, Input, Select, Tabs } from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import { TabPaneProps } from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {languages} from '../../../i18n' import { languages } from '../../../i18n'
export const BaseDataTab: React.FC<TabPaneProps> = props => { export const BaseDataTab: React.FC<TabPaneProps> = (props) => {
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.Item <Form.Item
@ -45,7 +45,7 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
message: 'Please select a role', message: 'Please select a role',
}, },
]} ]}
getValueFromEvent={e => { getValueFromEvent={(e) => {
switch (e) { switch (e) {
case 'superuser': case 'superuser':
return ['user', 'admin', 'superuser'] return ['user', 'admin', 'superuser']
@ -55,7 +55,7 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
return ['user'] return ['user']
} }
}} }}
getValueProps={v => { getValueProps={(v) => {
let role = 'user' let role = 'user'
if (v && v.includes('superuser')) { if (v && v.includes('superuser')) {
@ -65,12 +65,16 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
} }
return { return {
value: role value: role,
} }
}} }}
> >
<Select> <Select>
{['user', 'admin', 'superuser'].map(role => <Select.Option value={role} key={role}>{role.toUpperCase()}</Select.Option> )} {['user', 'admin', 'superuser'].map((role) => (
<Select.Option value={role} key={role}>
{role.toUpperCase()}
</Select.Option>
))}
</Select> </Select>
</Form.Item> </Form.Item>
@ -85,24 +89,21 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
]} ]}
> >
<Select> <Select>
{languages.map(language => <Select.Option value={language} key={language}>{language.toUpperCase()}</Select.Option> )} {languages.map((language) => (
<Select.Option value={language} key={language}>
{language.toUpperCase()}
</Select.Option>
))}
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item <Form.Item label="First Name" name={['user', 'firstName']}>
label="First Name" <Input />
name={['user', 'firstName']}
>
<Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label="Last Name" name={['user', 'lastName']}>
label="Last Name" <Input />
name={['user', 'lastName']}
>
<Input />
</Form.Item> </Form.Item>
</Tabs.TabPane> </Tabs.TabPane>
) )
} }

View File

@ -1,16 +1,15 @@
import {Tag} from "antd" import { Tag } from 'antd'
import React, {CSSProperties} from 'react' import React, { CSSProperties } from 'react'
interface Props { interface Props {
roles: string[] roles: string[]
} }
export const UserRole: React.FC<Props> = props => { export const UserRole: React.FC<Props> = (props) => {
let color let color
let level = 'unknown' let level = 'unknown'
const css: CSSProperties = {} const css: CSSProperties = {}
if (props.roles.includes('superuser')) { if (props.roles.includes('superuser')) {
color = 'red' color = 'red'
level = 'superuser' level = 'superuser'
@ -24,10 +23,7 @@ export const UserRole: React.FC<Props> = props => {
} }
return ( return (
<Tag <Tag color={color} style={css}>
color={color}
style={css}
>
{level.toUpperCase()} {level.toUpperCase()}
</Tag> </Tag>
) )

View File

@ -1,19 +1,19 @@
import {useQuery} from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import {AxiosRequestConfig} from 'axios' import { AxiosRequestConfig } from 'axios'
import {useRouter} from 'next/router' import { useRouter } from 'next/router'
import React, {useEffect, useState} from 'react' import React, { useEffect, useState } from 'react'
import {useTranslation} from 'react-i18next' 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 () => { export const clearAuth = async (): Promise<void> => {
localStorage.removeItem('access') localStorage.removeItem('access')
localStorage.removeItem('refresh') localStorage.removeItem('refresh')
// TODO logout on server! // TODO logout on server!
} }
export const setAuth = (access, refresh) => { export const setAuth = (access: string, refresh: string): void => {
localStorage.setItem('access', access) localStorage.setItem('access', access)
localStorage.setItem('refresh', refresh) localStorage.setItem('refresh', refresh)
} }
@ -37,12 +37,14 @@ export const authConfig = async (config: AxiosRequestConfig = {}): Promise<Axios
return config return config
} }
export const withAuth = (Component, roles: string[] = []): React.FC => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
return props => { export const withAuth = (Component: any, roles: string[] = []): React.FC => {
// eslint-disable-next-line react/display-name
return (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter() const router = useRouter()
const [access, setAccess] = useState(false) const [access, setAccess] = useState(false)
const {loading, data, error} = useQuery<MeQueryData>(ME_QUERY) const { loading, data, error } = useQuery<MeQueryData>(ME_QUERY)
useEffect(() => { useEffect(() => {
if (roles.length === 0) { if (roles.length === 0) {
@ -67,10 +69,7 @@ export const withAuth = (Component, roles: string[] = []): React.FC => {
return return
} }
const next = roles const next = roles.map((role) => data.me.roles.includes(role)).filter((p) => p).length > 0
.map(role => data.me.roles.includes(role))
.filter(p => p)
.length > 0
setAccess(next) setAccess(next)
@ -88,5 +87,5 @@ export const withAuth = (Component, roles: string[] = []): React.FC => {
} }
return <Component me={data && data.me} {...props} /> return <Component me={data && data.me} {...props} />
}; }
} }

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminFormPageFragment { export interface AdminFormPageFragment {
show: boolean show: boolean
@ -102,7 +102,7 @@ export const ADMIN_FORM_FRAGMENT = gql`
language language
showFooter showFooter
isLive isLive
fields { fields {
id id
title title
@ -110,13 +110,13 @@ export const ADMIN_FORM_FRAGMENT = gql`
description description
required required
value value
options { options {
key key
title title
value value
} }
logicJump { logicJump {
fieldA fieldA
valueB valueB
@ -129,7 +129,7 @@ export const ADMIN_FORM_FRAGMENT = gql`
shape shape
} }
} }
selfNotifications { selfNotifications {
enabled enabled
subject subject

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminProfileFragment { export interface AdminProfileFragment {
id: string id: string

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminUserFragment { export interface AdminUserFragment {
id: string id: string

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface FormPageFragment { export interface FormPageFragment {
show: boolean show: boolean
@ -77,7 +77,7 @@ export const FORM_FRAGMENT = gql`
title title
language language
showFooter showFooter
fields { fields {
id id
title title
@ -85,13 +85,13 @@ export const FORM_FRAGMENT = gql`
description description
required required
value value
options { options {
key key
title title
value value
} }
logicJump { logicJump {
fieldA fieldA
valueB valueB
@ -104,7 +104,7 @@ export const FORM_FRAGMENT = gql`
shape shape
} }
} }
design { design {
colors { colors {
backgroundColor backgroundColor
@ -116,7 +116,7 @@ export const FORM_FRAGMENT = gql`
} }
font font
} }
startPage { startPage {
show show
title title
@ -131,7 +131,7 @@ export const FORM_FRAGMENT = gql`
color color
} }
} }
endPage { endPage {
show show
title title

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface SubmissionFragment { export interface SubmissionFragment {
id?: string id?: string

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
import {ADMIN_FORM_FRAGMENT, AdminFormFragment} from '../fragment/admin.form.fragment' import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
export interface AdminFormCreateMutationData { export interface AdminFormCreateMutationData {
form: AdminFormFragment form: AdminFormFragment
@ -15,6 +15,6 @@ export const ADMIN_FORM_CREATE_MUTATION = gql`
...AdminForm ...AdminForm
} }
} }
${ADMIN_FORM_FRAGMENT} ${ADMIN_FORM_FRAGMENT}
` `

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminFormDeleteMutationData { export interface AdminFormDeleteMutationData {
form: { form: {

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
import {ADMIN_FORM_FRAGMENT, AdminFormFragment} from '../fragment/admin.form.fragment' import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
export interface AdminFormUpdateMutationData { export interface AdminFormUpdateMutationData {
form: AdminFormFragment form: AdminFormFragment
@ -15,6 +15,6 @@ export const ADMIN_FORM_UPDATE_MUTATION = gql`
...AdminForm ...AdminForm
} }
} }
${ADMIN_FORM_FRAGMENT} ${ADMIN_FORM_FRAGMENT}
` `

View File

@ -1,6 +1,6 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
import {ADMIN_PROFILE_FRAGMENT} from '../fragment/admin.profile.fragment' import { ADMIN_PROFILE_FRAGMENT } from '../fragment/admin.profile.fragment'
import {AdminUserFragment} from '../fragment/admin.user.fragment' import { AdminUserFragment } from '../fragment/admin.user.fragment'
export interface AdminProfileUpdateMutationData { export interface AdminProfileUpdateMutationData {
user: AdminUserFragment user: AdminUserFragment
@ -16,6 +16,6 @@ export const ADMIN_PROFILE_UPDATE_MUTATION = gql`
...AdminProfile ...AdminProfile
} }
} }
${ADMIN_PROFILE_FRAGMENT} ${ADMIN_PROFILE_FRAGMENT}
` `

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminUserDeleteMutationData { export interface AdminUserDeleteMutationData {
form: { form: {

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
import {ADMIN_USER_FRAGMENT, AdminUserFragment} from '../fragment/admin.user.fragment' import { ADMIN_USER_FRAGMENT, AdminUserFragment } from '../fragment/admin.user.fragment'
export interface AdminUserUpdateMutationData { export interface AdminUserUpdateMutationData {
user: AdminUserFragment user: AdminUserFragment
@ -15,6 +15,6 @@ export const ADMIN_USER_UPDATE_MUTATION = gql`
...AdminUser ...AdminUser
} }
} }
${ADMIN_USER_FRAGMENT} ${ADMIN_USER_FRAGMENT}
` `

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export const LOGIN_MUTATION = gql` export const LOGIN_MUTATION = gql`
mutation login($username: String!, $password: String!) { mutation login($username: String!, $password: String!) {

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export const REGISTER_MUTATION = gql` export const REGISTER_MUTATION = gql`
mutation register($user: UserCreateInput!) { mutation register($user: UserCreateInput!) {

View File

@ -1,7 +1,7 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export const SUBMISSION_FINISH_MUTATION = gql` export const SUBMISSION_FINISH_MUTATION = gql`
mutation start($submission: ID!,$field: SubmissionSetFieldInput!) { mutation start($submission: ID!, $field: SubmissionSetFieldInput!) {
submission: submissionSetField(submission: $submission, field: $field) { submission: submissionSetField(submission: $submission, field: $field) {
id id
percentageComplete percentageComplete

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface SubmissionSetFieldMutationData { export interface SubmissionSetFieldMutationData {
submission: { submission: {
@ -17,7 +17,7 @@ export interface SubmissionSetFieldMutationVariables {
} }
export const SUBMISSION_SET_FIELD_MUTATION = gql` export const SUBMISSION_SET_FIELD_MUTATION = gql`
mutation start($submission: ID!,$field: SubmissionSetFieldInput!) { mutation start($submission: ID!, $field: SubmissionSetFieldInput!) {
submission: submissionSetField(submission: $submission, field: $field) { submission: submissionSetField(submission: $submission, field: $field) {
id id
percentageComplete percentageComplete

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface SubmissionStartMutationData { export interface SubmissionStartMutationData {
submission: { submission: {
@ -19,7 +19,7 @@ export interface SubmissionStartMutationVariables {
} }
export const SUBMISSION_START_MUTATION = gql` export const SUBMISSION_START_MUTATION = gql`
mutation start($form: ID!,$submission: SubmissionStartInput!) { mutation start($form: ID!, $submission: SubmissionStartInput!) {
submission: submissionStart(form: $form, submission: $submission) { submission: submissionStart(form: $form, submission: $submission) {
id id
percentageComplete percentageComplete

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
import {ADMIN_FORM_FRAGMENT, AdminFormFragment} from '../fragment/admin.form.fragment' import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
export interface AdminFormQueryData { export interface AdminFormQueryData {
form: AdminFormFragment form: AdminFormFragment
@ -10,11 +10,11 @@ export interface AdminFormQueryVariables {
} }
export const ADMIN_FORM_QUERY = gql` export const ADMIN_FORM_QUERY = gql`
query form($id: ID!){ query form($id: ID!) {
form:getFormById(id: $id) { form: getFormById(id: $id) {
...AdminForm ...AdminForm
} }
} }
${ADMIN_FORM_FRAGMENT} ${ADMIN_FORM_FRAGMENT}
` `

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminPagerFormEntryQueryData { export interface AdminPagerFormEntryQueryData {
id: string id: string
@ -30,7 +30,7 @@ export interface AdminPagerFormQueryVariables {
} }
export const ADMIN_PAGER_FORM_QUERY = gql` export const ADMIN_PAGER_FORM_QUERY = gql`
query pager($start: Int, $limit: Int){ query pager($start: Int, $limit: Int) {
pager: listForms(start: $start, limit: $limit) { pager: listForms(start: $start, limit: $limit) {
entries { entries {
id id
@ -45,7 +45,7 @@ export const ADMIN_PAGER_FORM_QUERY = gql`
username username
} }
} }
total total
limit limit
start start
} }

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminPagerSubmissionFormFieldQueryData { export interface AdminPagerSubmissionFormFieldQueryData {
title: string title: string
@ -56,13 +56,13 @@ export interface AdminPagerSubmissionQueryVariables {
} }
export const ADMIN_PAGER_SUBMISSION_QUERY = gql` export const ADMIN_PAGER_SUBMISSION_QUERY = gql`
query pager($form: ID!, $start: Int, $limit: Int){ query pager($form: ID!, $start: Int, $limit: Int) {
form: getFormById(id: $form) { form: getFormById(id: $form) {
id id
title title
isLive isLive
} }
pager: listSubmissions(form: $form, start: $start, limit: $limit) { pager: listSubmissions(form: $form, start: $start, limit: $limit) {
entries { entries {
id id
@ -78,19 +78,19 @@ export const ADMIN_PAGER_SUBMISSION_QUERY = gql`
type type
name name
} }
fields { fields {
id id
value value
type type
field { field {
title title
required required
} }
} }
} }
total total
limit limit
start start
} }

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminPagerUserEntryQueryData { export interface AdminPagerUserEntryQueryData {
id: string id: string
@ -24,7 +24,7 @@ export interface AdminPagerUserQueryVariables {
} }
export const ADMIN_PAGER_USER_QUERY = gql` export const ADMIN_PAGER_USER_QUERY = gql`
query pager($start: Int, $limit: Int){ query pager($start: Int, $limit: Int) {
pager: listUsers(start: $start, limit: $limit) { pager: listUsers(start: $start, limit: $limit) {
entries { entries {
id id
@ -33,7 +33,7 @@ export const ADMIN_PAGER_USER_QUERY = gql`
email email
created created
} }
total total
limit limit
start start
} }

View File

@ -1,19 +1,18 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
import {ADMIN_PROFILE_FRAGMENT, AdminProfileFragment} from '../fragment/admin.profile.fragment' import { ADMIN_PROFILE_FRAGMENT, AdminProfileFragment } from '../fragment/admin.profile.fragment'
export interface AdminProfileQueryData { export interface AdminProfileQueryData {
user: AdminProfileFragment user: AdminProfileFragment
} }
export interface AdminProfileQueryVariables { export interface AdminProfileQueryVariables {}
}
export const ADMIN_PROFILE_QUERY = gql` export const ADMIN_PROFILE_QUERY = gql`
query profile { query profile {
user:me { user: me {
...AdminProfile ...AdminProfile
} }
} }
${ADMIN_PROFILE_FRAGMENT} ${ADMIN_PROFILE_FRAGMENT}
` `

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface AdminStatisticQueryData { export interface AdminStatisticQueryData {
forms: { forms: {
@ -12,13 +12,12 @@ export interface AdminStatisticQueryData {
} }
} }
export interface AdminStatisticQueryVariables { export interface AdminStatisticQueryVariables {}
}
export const ADMIN_STATISTIC_QUERY = gql` export const ADMIN_STATISTIC_QUERY = gql`
query { query {
forms: getFormStatistic { forms: getFormStatistic {
total total
} }
submissions: getSubmissionStatistic { submissions: getSubmissionStatistic {
total total

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
import {ADMIN_USER_FRAGMENT, AdminUserFragment} from '../fragment/admin.user.fragment' import { ADMIN_USER_FRAGMENT, AdminUserFragment } from '../fragment/admin.user.fragment'
export interface AdminUserQueryData { export interface AdminUserQueryData {
user: AdminUserFragment user: AdminUserFragment
@ -10,11 +10,11 @@ export interface AdminUserQueryVariables {
} }
export const ADMIN_USER_QUERY = gql` export const ADMIN_USER_QUERY = gql`
query user($id: ID!){ query user($id: ID!) {
user:getUserById(id: $id) { user: getUserById(id: $id) {
...AdminUser ...AdminUser
} }
} }
${ADMIN_USER_FRAGMENT} ${ADMIN_USER_FRAGMENT}
` `

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
import {FORM_FRAGMENT, FormFragment} from '../fragment/form.fragment' import { FORM_FRAGMENT, FormFragment } from '../fragment/form.fragment'
export interface FormQueryData { export interface FormQueryData {
form: FormFragment form: FormFragment
@ -10,11 +10,11 @@ export interface FormQueryVariables {
} }
export const FORM_QUERY = gql` export const FORM_QUERY = gql`
query form($id: ID!){ query form($id: ID!) {
form:getFormById(id: $id) { form: getFormById(id: $id) {
...Form ...Form
} }
} }
${FORM_FRAGMENT} ${FORM_FRAGMENT}
` `

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface MeQueryData { export interface MeQueryData {
me: { me: {
@ -11,7 +11,7 @@ export interface MeQueryData {
export const ME_QUERY = gql` export const ME_QUERY = gql`
query { query {
me { me {
id id
roles roles
username username
} }

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost' import { gql } from 'apollo-boost'
export interface SettingsQueryData { export interface SettingsQueryData {
disabledSignUp: { disabledSignUp: {
@ -7,9 +7,9 @@ export interface SettingsQueryData {
} }
export const SETTINGS_QUERY = gql` export const SETTINGS_QUERY = gql`
query { query {
disabledSignUp: getByKey (key: "SIGNUP_DISABLED") { disabledSignUp: getByKey(key: "SIGNUP_DISABLED") {
value: isTrue value: isTrue
}
} }
}
` `

View File

@ -1,17 +1,17 @@
import {ApolloProvider} from '@apollo/react-common' import { ApolloProvider } from '@apollo/react-common'
import {buildAxiosFetch} from '@lifeomic/axios-fetch' import { buildAxiosFetch } from '@lifeomic/axios-fetch'
import 'antd/dist/antd.css' import 'antd/dist/antd.css'
import ApolloClient from 'apollo-boost' import ApolloClient from 'apollo-boost'
import 'assets/global.scss' import 'assets/global.scss'
import 'assets/variables.scss' import 'assets/variables.scss'
import axios from 'axios' import axios from 'axios'
import {authConfig} from 'components/with.auth' import { authConfig } from 'components/with.auth'
import 'i18n' import 'i18n'
import {AppProps} from 'next/app' import { AppProps } from 'next/app'
import getConfig from 'next/config' import getConfig from 'next/config'
import Head from 'next/head' 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()
@ -20,7 +20,7 @@ const client = new ApolloClient({
fetch: buildAxiosFetch(axios), fetch: buildAxiosFetch(axios),
request: async (operation): Promise<void> => { request: async (operation): Promise<void> => {
operation.setContext(await authConfig()) operation.setContext(await authConfig())
} },
}) })
const App: React.FC<AppProps> = ({ Component, pageProps }) => { const App: React.FC<AppProps> = ({ Component, pageProps }) => {

View File

@ -1,32 +1,35 @@
import {useMutation, useQuery} from '@apollo/react-hooks' import { useMutation, useQuery } from '@apollo/react-hooks'
import {Button, Form, Input, message, Tabs} from 'antd' import { Button, Form, Input, message, Tabs } from 'antd'
import {useForm} from 'antd/lib/form/Form' import { useForm } from 'antd/lib/form/Form'
import {cleanInput} from 'components/clean.input' import { cleanInput } from 'components/clean.input'
import {BaseDataTab} from 'components/form/admin/base.data.tab' import { BaseDataTab } from 'components/form/admin/base.data.tab'
import {DesignTab} from 'components/form/admin/design.tab' import { DesignTab } from 'components/form/admin/design.tab'
import {EndPageTab} from 'components/form/admin/end.page.tab' import { EndPageTab } from 'components/form/admin/end.page.tab'
import {FieldsTab} from 'components/form/admin/fields.tab' import { FieldsTab } from 'components/form/admin/fields.tab'
import {RespondentNotificationsTab} from 'components/form/admin/respondent.notifications.tab' import { RespondentNotificationsTab } from 'components/form/admin/respondent.notifications.tab'
import {SelfNotificationsTab} from 'components/form/admin/self.notifications.tab' import { SelfNotificationsTab } from 'components/form/admin/self.notifications.tab'
import {StartPageTab} from 'components/form/admin/start.page.tab' import { StartPageTab } from 'components/form/admin/start.page.tab'
import Structure from 'components/structure' import Structure from 'components/structure'
import {withAuth} from 'components/with.auth' import { withAuth } from 'components/with.auth'
import { import {
AdminFormFieldFragment, AdminFormFieldFragment,
AdminFormFieldOptionFragment, AdminFormFieldOptionKeysFragment,
AdminFormFieldOptionKeysFragment
} from 'graphql/fragment/admin.form.fragment' } from 'graphql/fragment/admin.form.fragment'
import { import {
ADMIN_FORM_UPDATE_MUTATION, ADMIN_FORM_UPDATE_MUTATION,
AdminFormUpdateMutationData, AdminFormUpdateMutationData,
AdminFormUpdateMutationVariables AdminFormUpdateMutationVariables,
} from 'graphql/mutation/admin.form.update.mutation' } from 'graphql/mutation/admin.form.update.mutation'
import {ADMIN_FORM_QUERY, AdminFormQueryData, AdminFormQueryVariables} from 'graphql/query/admin.form.query' import {
import {NextPage} from 'next' ADMIN_FORM_QUERY,
AdminFormQueryData,
AdminFormQueryVariables,
} from 'graphql/query/admin.form.query'
import { NextPage } from 'next'
import Link from 'next/link' import Link from 'next/link'
import {useRouter} from 'next/router' import { useRouter } from 'next/router'
import React, {useState} from 'react' import React, { useState } from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
const Index: NextPage = () => { const Index: NextPage = () => {
const { t } = useTranslation() const { t } = useTranslation()
@ -34,19 +37,21 @@ const Index: NextPage = () => {
const [form] = useForm() const [form] = useForm()
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [fields, setFields] = useState<AdminFormFieldFragment[]>([]) const [fields, setFields] = useState<AdminFormFieldFragment[]>([])
const [update] = useMutation<AdminFormUpdateMutationData, AdminFormUpdateMutationVariables>(ADMIN_FORM_UPDATE_MUTATION) const [update] = useMutation<AdminFormUpdateMutationData, AdminFormUpdateMutationVariables>(
ADMIN_FORM_UPDATE_MUTATION
)
const processNext = (next: AdminFormQueryData): AdminFormQueryData => { const processNext = (next: AdminFormQueryData): AdminFormQueryData => {
next.form.fields = next.form.fields.map(field => { next.form.fields = next.form.fields.map((field) => {
const keys: AdminFormFieldOptionKeysFragment = {} const keys: AdminFormFieldOptionKeysFragment = {}
field.options.forEach(option => { field.options.forEach((option) => {
if (option.key) { if (option.key) {
keys[option.key] = option.value keys[option.key] = option.value
} }
}) })
field.options = field.options.filter(option => !option.key) field.options = field.options.filter((option) => !option.key)
field.optionKeys = keys field.optionKeys = keys
return field return field
}) })
@ -54,46 +59,55 @@ const Index: NextPage = () => {
return next return next
} }
const {data, loading, error} = useQuery<AdminFormQueryData, AdminFormQueryVariables>(ADMIN_FORM_QUERY, { const { data, loading } = useQuery<AdminFormQueryData, AdminFormQueryVariables>(
variables: { ADMIN_FORM_QUERY,
id: router.query.id as string {
}, variables: {
onCompleted: next => { id: router.query.id as string,
next = processNext(next) },
form.setFieldsValue(next) onCompleted: (next) => {
setFields(next.form.fields) next = processNext(next)
form.setFieldsValue(next)
setFields(next.form.fields)
},
} }
}) )
const save = async (formData: AdminFormQueryData) => { const save = async (formData: AdminFormQueryData) => {
setSaving(true) setSaving(true)
formData.form.fields = formData.form.fields.filter(e => e && e.type).map(({optionKeys, ...field}) => { formData.form.fields = formData.form.fields
const options = field.options .filter((e) => e && e.type)
.map(({ optionKeys, ...field }) => {
const options = field.options
if (optionKeys) { if (optionKeys) {
Object.keys(optionKeys).forEach((key) => { Object.keys(optionKeys).forEach((key) => {
if (!optionKeys[key]) { if (!optionKeys[key]) {
return return
} }
options.push({ options.push({
value: optionKeys[key], value: optionKeys[key],
key, key,
})
}) })
}) }
}
return { return {
...field, ...field,
options options,
} }
}) })
try { try {
const next = processNext((await update({ const next = processNext(
variables: cleanInput(formData), (
})).data) await update({
variables: cleanInput(formData),
})
).data
)
form.setFieldsValue(next) form.setFieldsValue(next)
setFields(next.form.fields) setFields(next.form.fields)
@ -124,18 +138,16 @@ const Index: NextPage = () => {
> >
<Button>{t('admin:submissions')}</Button> <Button>{t('admin:submissions')}</Button>
</Link>, </Link>,
<Button <Button key={'save'} onClick={form.submit} type={'primary'}>
key={'save'} {t('form:updateNow')}
onClick={form.submit} </Button>,
type={'primary'}
>{t('form:updateNow')}</Button>,
]} ]}
style={{paddingTop: 0}} style={{ paddingTop: 0 }}
> >
<Form <Form
form={form} form={form}
onFinish={save} onFinish={save}
onFinishFailed={errors => { onFinishFailed={() => {
// TODO process errors // TODO process errors
message.error(t('validation:mandatoryFieldsMissing')) message.error(t('validation:mandatoryFieldsMissing'))
}} }}
@ -148,7 +160,9 @@ const Index: NextPage = () => {
sm: { span: 18 }, sm: { span: 18 },
}} }}
> >
<Form.Item noStyle name={['form', 'id']}><Input type={'hidden'} /></Form.Item> <Form.Item noStyle name={['form', 'id']}>
<Input type={'hidden'} />
</Form.Item>
<Tabs> <Tabs>
<FieldsTab <FieldsTab

View File

@ -1,24 +1,25 @@
import {useQuery} from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import {Button, Progress, Table} from 'antd' import { Button, Progress, Table } from 'antd'
import {PaginationProps} from 'antd/es/pagination' import { PaginationProps } from 'antd/es/pagination'
import {ColumnsType} from 'antd/lib/table/interface' import { ProgressProps } from 'antd/lib/progress'
import {DateTime} from 'components/date.time' import { ColumnsType } from 'antd/lib/table/interface'
import { DateTime } from 'components/date.time'
import Structure from 'components/structure' import Structure from 'components/structure'
import {TimeAgo} from 'components/time.ago' import { TimeAgo } from 'components/time.ago'
import {withAuth} from 'components/with.auth' import { withAuth } from 'components/with.auth'
import dayjs from 'dayjs' import dayjs from 'dayjs'
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'
import React, {useState} from 'react' import React, { useState } from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {SubmissionValues} from '../../../../components/form/admin/submission.values' import { SubmissionValues } from '../../../../components/form/admin/submission.values'
import { import {
ADMIN_PAGER_SUBMISSION_QUERY, ADMIN_PAGER_SUBMISSION_QUERY,
AdminPagerSubmissionEntryQueryData, AdminPagerSubmissionEntryQueryData,
AdminPagerSubmissionFormQueryData, AdminPagerSubmissionFormQueryData,
AdminPagerSubmissionQueryData, AdminPagerSubmissionQueryData,
AdminPagerSubmissionQueryVariables AdminPagerSubmissionQueryVariables,
} from '../../../../graphql/query/admin.pager.submission.query' } from '../../../../graphql/query/admin.pager.submission.query'
const Submissions: NextPage = () => { const Submissions: NextPage = () => {
@ -29,48 +30,56 @@ const Submissions: NextPage = () => {
}) })
const [form, setForm] = useState<AdminPagerSubmissionFormQueryData>() const [form, setForm] = useState<AdminPagerSubmissionFormQueryData>()
const [entries, setEntries] = useState<AdminPagerSubmissionEntryQueryData[]>() const [entries, setEntries] = useState<AdminPagerSubmissionEntryQueryData[]>()
const {loading, refetch} = useQuery<AdminPagerSubmissionQueryData, AdminPagerSubmissionQueryVariables>(ADMIN_PAGER_SUBMISSION_QUERY, { const { loading, refetch } = useQuery<
AdminPagerSubmissionQueryData,
AdminPagerSubmissionQueryVariables
>(ADMIN_PAGER_SUBMISSION_QUERY, {
variables: { variables: {
form: router.query.id as string, form: router.query.id as string,
limit: pagination.pageSize, limit: pagination.pageSize,
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0 start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
}, },
onCompleted: ({pager, form}) => { onCompleted: ({ pager, form }) => {
setPagination({ setPagination({
...pagination, ...pagination,
total: pager.total, total: pager.total,
}) })
setForm(form) setForm(form)
setEntries(pager.entries) setEntries(pager.entries)
} },
}) })
const columns:ColumnsType<AdminPagerSubmissionEntryQueryData> = [ const columns: ColumnsType<AdminPagerSubmissionEntryQueryData> = [
{ {
title: t('submission:progress'), title: t('submission:progress'),
render: (row: AdminPagerSubmissionEntryQueryData) => { render(row: AdminPagerSubmissionEntryQueryData) {
let status: any = 'active' const props: ProgressProps = {
status: 'active',
if (row.percentageComplete >= 1) { percent: Math.round(row.percentageComplete * 100),
status = 'success'
} else if (dayjs().diff(dayjs(row.lastModified), 'hour') > 4) {
status = 'exception'
} }
return ( if (row.percentageComplete >= 1) {
<Progress percent={Math.round(row.percentageComplete * 100)} status={status} /> props.status = 'success'
) } else if (dayjs().diff(dayjs(row.lastModified), 'hour') > 4) {
} props.status = 'exception'
}
return <Progress {...props} />
},
}, },
{ {
title: t('submission:created'), title: t('submission:created'),
dataIndex: 'created', dataIndex: 'created',
render: date => <DateTime date={date} /> render(date) {
return <DateTime date={date} />
},
}, },
{ {
title: t('submission:lastModified'), title: t('submission:lastModified'),
dataIndex: 'lastModified', dataIndex: 'lastModified',
render: date => <TimeAgo date={date} /> render(date) {
return <TimeAgo date={date} />
},
}, },
] ]
@ -82,23 +91,20 @@ const Submissions: NextPage = () => {
breadcrumbs={[ breadcrumbs={[
{ href: '/admin', name: t('admin:home') }, { href: '/admin', name: t('admin:home') },
{ href: '/admin/forms', name: t('admin:forms') }, { href: '/admin/forms', name: t('admin:forms') },
{ href: '/admin/forms/[id]', name: loading || !form ? t('form:loading') : t('form:mange', { title: form.title }), as: `/admin/forms/${router.query.id}` }, {
href: '/admin/forms/[id]',
name: loading || !form ? t('form:loading') : t('form:mange', { title: form.title }),
as: `/admin/forms/${router.query.id}`,
},
]} ]}
padded={false} padded={false}
extra={[ extra={[
<Link <Link key={'edit'} href={'/admin/forms/[id]'} as={`/admin/forms/${router.query.id}`}>
key={'edit'}
href={'/admin/forms/[id]'}
as={`/admin/forms/${router.query.id}`}
>
<Button>{t('submission:edit')}</Button> <Button>{t('submission:edit')}</Button>
</Link>, </Link>,
<Button <Button key={'web'} href={`/form/${router.query.id}`} target={'_blank'} type={'primary'}>
key={'web'} {t('submission:add')}
href={`/form/${router.query.id}`} </Button>,
target={'_blank'}
type={'primary'}
>{t('submission:add')}</Button>,
]} ]}
> >
<Table <Table
@ -107,10 +113,14 @@ const Submissions: NextPage = () => {
rowKey={'id'} rowKey={'id'}
pagination={pagination} pagination={pagination}
expandable={{ expandable={{
expandedRowRender: record => <SubmissionValues form={form} submission={record} />, expandedRowRender(record) {
rowExpandable: record => record.percentageComplete > 0, return <SubmissionValues form={form} submission={record} />
},
rowExpandable(record) {
return record.percentageComplete > 0
},
}} }}
onChange={next => { onChange={(next) => {
setPagination(next) setPagination(next)
refetch() refetch()
}} }}

View File

@ -1,19 +1,19 @@
import {useMutation} from '@apollo/react-hooks' import { useMutation } from '@apollo/react-hooks'
import {Button, Form, Input, message, Tabs} from 'antd' import { Button, Form, Input, message, Tabs } from 'antd'
import {useForm} from 'antd/lib/form/Form' import { useForm } from 'antd/lib/form/Form'
import {cleanInput} from 'components/clean.input' import { cleanInput } from 'components/clean.input'
import {BaseDataTab} from 'components/form/admin/base.data.tab' import { BaseDataTab } from 'components/form/admin/base.data.tab'
import Structure from 'components/structure' import Structure from 'components/structure'
import {withAuth} from 'components/with.auth' import { withAuth } from 'components/with.auth'
import {AdminFormQueryData} from 'graphql/query/admin.form.query' import { AdminFormQueryData } from 'graphql/query/admin.form.query'
import {NextPage} from 'next' import { NextPage } from 'next'
import {useRouter} from 'next/router' import { useRouter } from 'next/router'
import React, {useState} from 'react' import React, { useState } from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
ADMIN_FORM_CREATE_MUTATION, ADMIN_FORM_CREATE_MUTATION,
AdminFormCreateMutationData, AdminFormCreateMutationData,
AdminFormCreateMutationVariables AdminFormCreateMutationVariables,
} from '../../../graphql/mutation/admin.form.create.mutation' } from '../../../graphql/mutation/admin.form.create.mutation'
const Create: NextPage = () => { const Create: NextPage = () => {
@ -21,15 +21,19 @@ const Create: NextPage = () => {
const router = useRouter() const router = useRouter()
const [form] = useForm() const [form] = useForm()
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [create] = useMutation<AdminFormCreateMutationData, AdminFormCreateMutationVariables>(ADMIN_FORM_CREATE_MUTATION) const [create] = useMutation<AdminFormCreateMutationData, AdminFormCreateMutationVariables>(
ADMIN_FORM_CREATE_MUTATION
)
const save = async (formData: AdminFormQueryData) => { const save = async (formData: AdminFormQueryData) => {
setSaving(true) setSaving(true)
try { try {
const next = (await create({ const next = (
variables: cleanInput(formData), await create({
})).data variables: cleanInput(formData),
})
).data
message.success(t('form:created')) message.success(t('form:created'))
@ -52,18 +56,16 @@ const Create: NextPage = () => {
{ href: '/admin/forms', name: t('admin:forms') }, { href: '/admin/forms', name: t('admin:forms') },
]} ]}
extra={[ extra={[
<Button <Button key={'create'} onClick={form.submit} type={'primary'}>
key={'create'} {t('form:createNow')}
onClick={form.submit} </Button>,
type={'primary'}
>{t('form:createNow')}</Button>,
]} ]}
style={{paddingTop: 0}} style={{ paddingTop: 0 }}
> >
<Form <Form
form={form} form={form}
onFinish={save} onFinish={save}
onFinishFailed={errors => { onFinishFailed={() => {
// TODO process errors // TODO process errors
message.error(t('validation:mandatoryFieldsMissing')) message.error(t('validation:mandatoryFieldsMissing'))
}} }}
@ -76,7 +78,9 @@ const Create: NextPage = () => {
sm: { span: 18 }, sm: { span: 18 },
}} }}
> >
<Form.Item noStyle name={['form', 'id']}><Input type={'hidden'} /></Form.Item> <Form.Item noStyle name={['form', 'id']}>
<Input type={'hidden'} />
</Form.Item>
<Tabs> <Tabs>
<BaseDataTab key={'base_data'} tab={t('form:baseDataTab')} /> <BaseDataTab key={'base_data'} tab={t('form:baseDataTab')} />

View File

@ -1,27 +1,32 @@
import {DeleteOutlined, EditOutlined, GlobalOutlined, UnorderedListOutlined} from '@ant-design/icons/lib' import {
import {useMutation, useQuery} from '@apollo/react-hooks' DeleteOutlined,
import {Button, message, Popconfirm, Space, Table, Tooltip} from 'antd' EditOutlined,
import {PaginationProps} from 'antd/es/pagination' GlobalOutlined,
import {ColumnsType} from 'antd/lib/table/interface' UnorderedListOutlined,
import {DateTime} from 'components/date.time' } from '@ant-design/icons/lib'
import {FormIsLive} from 'components/form/admin/is.live' import { useMutation, useQuery } from '@apollo/react-hooks'
import { Button, message, Popconfirm, Space, Table, Tooltip } from 'antd'
import { PaginationProps } from 'antd/es/pagination'
import { ColumnsType } from 'antd/lib/table/interface'
import { DateTime } from 'components/date.time'
import { FormIsLive } from 'components/form/admin/is.live'
import Structure from 'components/structure' import Structure from 'components/structure'
import {TimeAgo} from 'components/time.ago' 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,
AdminPagerFormEntryQueryData, AdminPagerFormEntryQueryData,
AdminPagerFormQueryData, AdminPagerFormQueryData,
AdminPagerFormQueryVariables AdminPagerFormQueryVariables,
} from 'graphql/query/admin.pager.form.query' } from 'graphql/query/admin.pager.form.query'
import {NextPage} from 'next' import { NextPage } from 'next'
import Link from 'next/link' import Link from 'next/link'
import React, {useState} from 'react' import React, { useState } from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
ADMIN_FORM_DELETE_MUTATION, ADMIN_FORM_DELETE_MUTATION,
AdminFormDeleteMutationData, AdminFormDeleteMutationData,
AdminFormDeleteMutationVariables AdminFormDeleteMutationVariables,
} from '../../../graphql/mutation/admin.form.delete.mutation' } from '../../../graphql/mutation/admin.form.delete.mutation'
const Index: NextPage = () => { const Index: NextPage = () => {
@ -30,29 +35,35 @@ const Index: NextPage = () => {
pageSize: 25, pageSize: 25,
}) })
const [entries, setEntries] = useState<AdminPagerFormEntryQueryData[]>() const [entries, setEntries] = useState<AdminPagerFormEntryQueryData[]>()
const {loading, refetch} = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(ADMIN_PAGER_FORM_QUERY, { const { loading, refetch } = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(
variables: { ADMIN_PAGER_FORM_QUERY,
limit: pagination.pageSize, {
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0 variables: {
}, limit: pagination.pageSize,
onCompleted: ({pager}) => { start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
setPagination({ },
...pagination, onCompleted: ({ pager }) => {
total: pager.total, setPagination({
}) ...pagination,
setEntries(pager.entries) total: pager.total,
})
setEntries(pager.entries)
},
} }
}) )
const [executeDelete] = useMutation<AdminFormDeleteMutationData, AdminFormDeleteMutationVariables>(ADMIN_FORM_DELETE_MUTATION) const [executeDelete] = useMutation<
AdminFormDeleteMutationData,
AdminFormDeleteMutationVariables
>(ADMIN_FORM_DELETE_MUTATION)
const deleteForm = async (form) => { const deleteForm = async (form) => {
try { try {
await executeDelete({ await executeDelete({
variables: { variables: {
id: form.id id: form.id,
} },
}) })
const next = entries.filter(entry => entry.id !== form.id) const next = entries.filter((entry) => entry.id !== form.id)
if (next.length === 0) { if (next.length === 0) {
setPagination({ ...pagination, current: 1 }) setPagination({ ...pagination, current: 1 })
} else { } else {
@ -69,7 +80,9 @@ const Index: NextPage = () => {
{ {
title: t('form:row.isLive'), title: t('form:row.isLive'),
dataIndex: 'isLive', dataIndex: 'isLive',
render: live => <FormIsLive isLive={live} /> render(live) {
return <FormIsLive isLive={live} />
},
}, },
{ {
title: t('form:row.title'), title: t('form:row.title'),
@ -78,49 +91,55 @@ const Index: NextPage = () => {
{ {
title: t('form:row.admin'), title: t('form:row.admin'),
dataIndex: 'admin', dataIndex: 'admin',
render: user => ( render(user) {
<Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}> return (
<Tooltip title={user.email}> <Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}>
<Button type={'dashed'}>{user.username}</Button> <Tooltip title={user.email}>
</Tooltip> <Button type={'dashed'}>{user.username}</Button>
</Link> </Tooltip>
) </Link>
)
},
}, },
{ {
title: t('form:row.language'), title: t('form:row.language'),
dataIndex: 'language', dataIndex: 'language',
render: lang => t(`language:${lang}`) render(lang) {
return t(`language:${lang}`)
},
}, },
{ {
title: t('form:row.created'), title: t('form:row.created'),
dataIndex: 'created', dataIndex: 'created',
render: date => <DateTime date={date} /> render(date) {
return <DateTime date={date} />
},
}, },
{ {
title: t('form:row.lastModified'), title: t('form:row.lastModified'),
dataIndex: 'lastModified', dataIndex: 'lastModified',
render: date => <TimeAgo date={date} /> render(date) {
return <TimeAgo date={date} />
},
}, },
{ {
title: t('form:row.menu'), title: t('form:row.menu'),
align: 'right', align: 'right',
render: row => { render(row) {
return ( return (
<Space> <Space>
<Link <Link href={'/admin/forms/[id]/submissions'} as={`/admin/forms/${row.id}/submissions`}>
href={'/admin/forms/[id]/submissions'}
as={`/admin/forms/${row.id}/submissions`}
>
<Tooltip title={'Show Submissions'}> <Tooltip title={'Show Submissions'}>
<Button><UnorderedListOutlined /></Button> <Button>
<UnorderedListOutlined />
</Button>
</Tooltip> </Tooltip>
</Link> </Link>
<Link <Link href={'/admin/forms/[id]'} as={`/admin/forms/${row.id}`}>
href={'/admin/forms/[id]'} <Button type={'primary'}>
as={`/admin/forms/${row.id}`} <EditOutlined />
> </Button>
<Button type={'primary'}><EditOutlined /></Button>
</Link> </Link>
<Popconfirm <Popconfirm
@ -129,20 +148,19 @@ const Index: NextPage = () => {
okText={t('form:deleteNow')} okText={t('form:deleteNow')}
okButtonProps={{ danger: true }} okButtonProps={{ danger: true }}
> >
<Button danger><DeleteOutlined /></Button> <Button danger>
<DeleteOutlined />
</Button>
</Popconfirm> </Popconfirm>
<Tooltip title={row.isLive ? null : 'Not Public accessible!'}> <Tooltip title={row.isLive ? null : 'Not Public accessible!'}>
<Button <Button href={`/form/${row.id}`} target={'_blank'}>
href={`/form/${row.id}`}
target={'_blank'}
>
<GlobalOutlined /> <GlobalOutlined />
</Button> </Button>
</Tooltip> </Tooltip>
</Space> </Space>
) )
} },
}, },
] ]
@ -151,18 +169,11 @@ const Index: NextPage = () => {
title={t('admin:forms')} title={t('admin:forms')}
selected={'forms'} selected={'forms'}
loading={loading} loading={loading}
breadcrumbs={[ breadcrumbs={[{ href: '/admin', name: t('admin:home') }]}
{ href: '/admin', name: t('admin:home') },
]}
padded={false} padded={false}
extra={[ extra={[
<Link <Link key={'create'} href={'/admin/forms/create'}>
key={'create'} <Button type={'primary'}>{t('form:new')}</Button>
href={'/admin/forms/create'}
>
<Button
type={'primary'}
>{t('form:new')}</Button>
</Link>, </Link>,
]} ]}
> >
@ -171,7 +182,7 @@ const Index: NextPage = () => {
dataSource={entries} dataSource={entries}
rowKey={'id'} rowKey={'id'}
pagination={pagination} pagination={pagination}
onChange={next => { onChange={(next) => {
setPagination(next) setPagination(next)
refetch() refetch()
}} }}

View File

@ -1,26 +1,24 @@
import {useQuery} from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import {Col, Row, Statistic} from 'antd' import { Col, Row, Statistic } from 'antd'
import Structure from 'components/structure' import Structure from 'components/structure'
import {withAuth} from 'components/with.auth' import { withAuth } from 'components/with.auth'
import {NextPage} from 'next' import { NextPage } from 'next'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
ADMIN_STATISTIC_QUERY, ADMIN_STATISTIC_QUERY,
AdminStatisticQueryData, AdminStatisticQueryData,
AdminStatisticQueryVariables AdminStatisticQueryVariables,
} from '../../graphql/query/admin.statistic.query' } from '../../graphql/query/admin.statistic.query'
const Index: NextPage = () => { const Index: NextPage = () => {
const { t } = useTranslation() const { t } = useTranslation()
const {data, loading} = useQuery<AdminStatisticQueryData, AdminStatisticQueryVariables>(ADMIN_STATISTIC_QUERY) const { data, loading } = useQuery<AdminStatisticQueryData, AdminStatisticQueryVariables>(
ADMIN_STATISTIC_QUERY
)
return ( return (
<Structure <Structure title={t('admin:home')} selected={'home'} loading={loading}>
title={t('admin:home')}
selected={'home'}
loading={loading}
>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Statistic title={t('statistic:totalForms')} value={data && data.forms.total} /> <Statistic title={t('statistic:totalForms')} value={data && data.forms.total} />
@ -31,7 +29,10 @@ const Index: NextPage = () => {
</Col> </Col>
<Col span={8}> <Col span={8}>
<Statistic title={t('statistic:totalSubmissions')} value={data && data.submissions.total} /> <Statistic
title={t('statistic:totalSubmissions')}
value={data && data.submissions.total}
/>
</Col> </Col>
</Row> </Row>
</Structure> </Structure>

View File

@ -1,46 +1,51 @@
import {useMutation, useQuery} from '@apollo/react-hooks' import { useMutation, useQuery } from '@apollo/react-hooks'
import {Button, Form, Input, message, Select} from 'antd' import { Button, Form, Input, message, Select } from 'antd'
import {useForm} from 'antd/lib/form/Form' import { useForm } from 'antd/lib/form/Form'
import {NextPage} from 'next' import { NextPage } from 'next'
import {useRouter} from 'next/router' import React, { useState } from 'react'
import React, {useState} from 'react' import { useTranslation } from 'react-i18next'
import {useTranslation} from 'react-i18next' import { cleanInput } from '../../components/clean.input'
import {cleanInput} from '../../components/clean.input'
import Structure from '../../components/structure' import Structure from '../../components/structure'
import { import {
ADMIN_PROFILE_UPDATE_MUTATION, ADMIN_PROFILE_UPDATE_MUTATION,
AdminProfileUpdateMutationData, AdminProfileUpdateMutationData,
AdminProfileUpdateMutationVariables AdminProfileUpdateMutationVariables,
} from '../../graphql/mutation/admin.profile.update.mutation' } from '../../graphql/mutation/admin.profile.update.mutation'
import { import {
ADMIN_PROFILE_QUERY, ADMIN_PROFILE_QUERY,
AdminProfileQueryData, AdminProfileQueryData,
AdminProfileQueryVariables AdminProfileQueryVariables,
} from '../../graphql/query/admin.profile.query' } from '../../graphql/query/admin.profile.query'
import {AdminUserQueryData} from '../../graphql/query/admin.user.query' import { AdminUserQueryData } from '../../graphql/query/admin.user.query'
import {languages} from '../../i18n' import { languages } from '../../i18n'
const Profile: NextPage = () => { const Profile: NextPage = () => {
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter()
const [form] = useForm() const [form] = useForm()
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const {data, loading, error} = useQuery<AdminProfileQueryData, AdminProfileQueryVariables>(ADMIN_PROFILE_QUERY, { const { loading } = useQuery<AdminProfileQueryData, AdminProfileQueryVariables>(
onCompleted: next => { ADMIN_PROFILE_QUERY,
form.setFieldsValue(next) {
}, onCompleted: (next) => {
}) form.setFieldsValue(next)
},
}
)
const [update] = useMutation<AdminProfileUpdateMutationData, AdminProfileUpdateMutationVariables>(ADMIN_PROFILE_UPDATE_MUTATION) const [update] = useMutation<AdminProfileUpdateMutationData, AdminProfileUpdateMutationVariables>(
ADMIN_PROFILE_UPDATE_MUTATION
)
const save = async (formData: AdminUserQueryData) => { const save = async (formData: AdminUserQueryData) => {
setSaving(true) setSaving(true)
try { try {
const next = (await update({ const next = (
variables: cleanInput(formData), await update({
})).data variables: cleanInput(formData),
})
).data
form.setFieldsValue(next) form.setFieldsValue(next)
@ -53,27 +58,22 @@ const Profile: NextPage = () => {
setSaving(false) setSaving(false)
} }
return ( return (
<Structure <Structure
loading={loading || saving} loading={loading || saving}
title={t('admin:profile')} title={t('admin:profile')}
selected={'profile'} selected={'profile'}
breadcrumbs={[ breadcrumbs={[{ href: '/admin', name: t('admin:home') }]}
{ href: '/admin', name: t('admin:home') },
]}
extra={[ extra={[
<Button <Button key={'save'} onClick={form.submit} type={'primary'}>
key={'save'} {t('profile:updateNow')}
onClick={form.submit} </Button>,
type={'primary'}
>{t('profile:updateNow')}</Button>,
]} ]}
> >
<Form <Form
form={form} form={form}
onFinish={save} onFinish={save}
onFinishFailed={errors => { onFinishFailed={() => {
// TODO process errors // TODO process errors
message.error(t('validation:mandatoryFieldsMissing')) message.error(t('validation:mandatoryFieldsMissing'))
}} }}
@ -86,7 +86,9 @@ const Profile: NextPage = () => {
sm: { span: 18 }, sm: { span: 18 },
}} }}
> >
<Form.Item noStyle name={['user', 'id']}><Input type={'hidden'} /></Form.Item> <Form.Item noStyle name={['user', 'id']}>
<Input type={'hidden'} />
</Form.Item>
<Form.Item <Form.Item
label={t('profile:username')} label={t('profile:username')}
@ -129,22 +131,20 @@ const Profile: NextPage = () => {
]} ]}
> >
<Select> <Select>
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )} {languages.map((language) => (
<Select.Option value={language} key={language}>
{t(`language:${language}`)}
</Select.Option>
))}
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t('profile:firstName')} name={['user', 'firstName']}>
label={t('profile:firstName')} <Input />
name={['user', 'firstName']}
>
<Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t('profile:lastName')} name={['user', 'lastName']}>
label={t('profile:lastName')} <Input />
name={['user', 'lastName']}
>
<Input />
</Form.Item> </Form.Item>
</Form> </Form>
</Structure> </Structure>

View File

@ -1,20 +1,24 @@
import {useMutation, useQuery} from '@apollo/react-hooks' import { useMutation, useQuery } from '@apollo/react-hooks'
import {Button, Form, Input, message, Tabs} from 'antd' import { Button, Form, Input, message, Tabs } from 'antd'
import {useForm} from 'antd/lib/form/Form' import { useForm } from 'antd/lib/form/Form'
import Structure from 'components/structure' import Structure from 'components/structure'
import {withAuth} from 'components/with.auth' import { withAuth } from 'components/with.auth'
import {NextPage} from 'next' import { NextPage } from 'next'
import {useRouter} from 'next/router' import { useRouter } from 'next/router'
import React, {useState} from 'react' import React, { useState } from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {cleanInput} from '../../../../components/clean.input' import { cleanInput } from '../../../../components/clean.input'
import {BaseDataTab} from '../../../../components/user/admin/base.data.tab' import { BaseDataTab } from '../../../../components/user/admin/base.data.tab'
import { import {
ADMIN_USER_UPDATE_MUTATION, ADMIN_USER_UPDATE_MUTATION,
AdminUserUpdateMutationData, AdminUserUpdateMutationData,
AdminUserUpdateMutationVariables AdminUserUpdateMutationVariables,
} from '../../../../graphql/mutation/admin.user.update.mutation' } from '../../../../graphql/mutation/admin.user.update.mutation'
import {ADMIN_USER_QUERY, AdminUserQueryData, AdminUserQueryVariables} from '../../../../graphql/query/admin.user.query' import {
ADMIN_USER_QUERY,
AdminUserQueryData,
AdminUserQueryVariables,
} from '../../../../graphql/query/admin.user.query'
const Index: NextPage = () => { const Index: NextPage = () => {
const { t } = useTranslation() const { t } = useTranslation()
@ -22,23 +26,30 @@ const Index: NextPage = () => {
const [form] = useForm() const [form] = useForm()
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const {data, loading, error} = useQuery<AdminUserQueryData, AdminUserQueryVariables>(ADMIN_USER_QUERY, { const { data, loading } = useQuery<AdminUserQueryData, AdminUserQueryVariables>(
variables: { ADMIN_USER_QUERY,
id: router.query.id as string {
}, variables: {
onCompleted: next => { id: router.query.id as string,
form.setFieldsValue(next) },
}, onCompleted: (next) => {
}) form.setFieldsValue(next)
},
}
)
const [update] = useMutation<AdminUserUpdateMutationData, AdminUserUpdateMutationVariables>(ADMIN_USER_UPDATE_MUTATION) const [update] = useMutation<AdminUserUpdateMutationData, AdminUserUpdateMutationVariables>(
ADMIN_USER_UPDATE_MUTATION
)
const save = async (formData: AdminUserQueryData) => { const save = async (formData: AdminUserQueryData) => {
setSaving(true) setSaving(true)
try { try {
const next = (await update({ const next = (
variables: cleanInput(formData), await update({
})).data variables: cleanInput(formData),
})
).data
form.setFieldsValue(next) form.setFieldsValue(next)
@ -61,18 +72,16 @@ const Index: NextPage = () => {
{ href: '/admin/users', name: t('admin:users') }, { href: '/admin/users', name: t('admin:users') },
]} ]}
extra={[ extra={[
<Button <Button key={'save'} onClick={form.submit} type={'primary'}>
key={'save'} {t('user:updateNow')}
onClick={form.submit} </Button>,
type={'primary'}
>{t('user:updateNow')}</Button>,
]} ]}
style={{paddingTop: 0}} style={{ paddingTop: 0 }}
> >
<Form <Form
form={form} form={form}
onFinish={save} onFinish={save}
onFinishFailed={errors => { onFinishFailed={() => {
// TODO process errors // TODO process errors
message.error(t('validation:mandatoryFieldsMissing')) message.error(t('validation:mandatoryFieldsMissing'))
}} }}
@ -85,13 +94,12 @@ const Index: NextPage = () => {
sm: { span: 18 }, sm: { span: 18 },
}} }}
> >
<Form.Item noStyle name={['user', 'id']}><Input type={'hidden'} /></Form.Item> <Form.Item noStyle name={['user', 'id']}>
<Input type={'hidden'} />
</Form.Item>
<Tabs> <Tabs>
<BaseDataTab <BaseDataTab key={'base_data'} tab={t('user:baseData')} />
key={'base_data'}
tab={t('user:baseData')}
/>
</Tabs> </Tabs>
</Form> </Form>
</Structure> </Structure>

View File

@ -1,26 +1,26 @@
import {DeleteOutlined, EditOutlined} from '@ant-design/icons/lib' import { DeleteOutlined, EditOutlined } from '@ant-design/icons/lib'
import {useMutation, useQuery} from '@apollo/react-hooks' import { useMutation, useQuery } from '@apollo/react-hooks'
import {Button, message, Popconfirm, Space, Table, Tag} from 'antd' import { Button, message, Popconfirm, Space, Table, Tag } from 'antd'
import {PaginationProps} from 'antd/es/pagination' import { PaginationProps } from 'antd/es/pagination'
import {ColumnsType} from 'antd/lib/table/interface' import { ColumnsType } from 'antd/lib/table/interface'
import Structure from 'components/structure' import Structure from 'components/structure'
import {withAuth} from 'components/with.auth' import { withAuth } from 'components/with.auth'
import {NextPage} from 'next' import { NextPage } from 'next'
import Link from 'next/link' import Link from 'next/link'
import React, {useState} from 'react' import React, { useState } from 'react'
import {useTranslation} from 'react-i18next' import { useTranslation } from 'react-i18next'
import {DateTime} from '../../../components/date.time' import { DateTime } from '../../../components/date.time'
import {UserRole} from '../../../components/user/role' import { UserRole } from '../../../components/user/role'
import { import {
ADMIN_USER_DELETE_MUTATION, ADMIN_USER_DELETE_MUTATION,
AdminUserDeleteMutationData, AdminUserDeleteMutationData,
AdminUserDeleteMutationVariables AdminUserDeleteMutationVariables,
} from '../../../graphql/mutation/admin.user.delete.mutation' } from '../../../graphql/mutation/admin.user.delete.mutation'
import { import {
ADMIN_PAGER_USER_QUERY, ADMIN_PAGER_USER_QUERY,
AdminPagerUserEntryQueryData, AdminPagerUserEntryQueryData,
AdminPagerUserQueryData, AdminPagerUserQueryData,
AdminPagerUserQueryVariables AdminPagerUserQueryVariables,
} from '../../../graphql/query/admin.pager.user.query' } from '../../../graphql/query/admin.pager.user.query'
const Index: NextPage = () => { const Index: NextPage = () => {
@ -29,29 +29,35 @@ const Index: NextPage = () => {
pageSize: 10, pageSize: 10,
}) })
const [entries, setEntries] = useState<AdminPagerUserEntryQueryData[]>() const [entries, setEntries] = useState<AdminPagerUserEntryQueryData[]>()
const {loading, refetch} = useQuery<AdminPagerUserQueryData, AdminPagerUserQueryVariables>(ADMIN_PAGER_USER_QUERY, { const { loading, refetch } = useQuery<AdminPagerUserQueryData, AdminPagerUserQueryVariables>(
variables: { ADMIN_PAGER_USER_QUERY,
limit: pagination.pageSize, {
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0 variables: {
}, limit: pagination.pageSize,
onCompleted: ({pager}) => { start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
setPagination({ },
...pagination, onCompleted: ({ pager }) => {
total: pager.total, setPagination({
}) ...pagination,
setEntries(pager.entries) total: pager.total,
})
setEntries(pager.entries)
},
} }
}) )
const [executeDelete] = useMutation<AdminUserDeleteMutationData, AdminUserDeleteMutationVariables>(ADMIN_USER_DELETE_MUTATION) const [executeDelete] = useMutation<
AdminUserDeleteMutationData,
AdminUserDeleteMutationVariables
>(ADMIN_USER_DELETE_MUTATION)
const deleteUser = async (form) => { const deleteUser = async (form) => {
try { try {
await executeDelete({ await executeDelete({
variables: { variables: {
id: form.id id: form.id,
} },
}) })
const next = entries.filter(entry => entry.id !== form.id) const next = entries.filter((entry) => entry.id !== form.id)
if (next.length === 0) { if (next.length === 0) {
setPagination({ ...pagination, current: 1 }) setPagination({ ...pagination, current: 1 })
} else { } else {
@ -67,28 +73,33 @@ const Index: NextPage = () => {
{ {
title: t('user:row.roles'), title: t('user:row.roles'),
dataIndex: 'roles', dataIndex: 'roles',
render: roles => <UserRole roles={roles} /> render(roles) {
return <UserRole roles={roles} />
},
}, },
{ {
title: t('user:row.email'), title: t('user:row.email'),
render: row => <Tag color={row.verifiedEmail ? 'lime' : 'volcano' }>{row.email}</Tag> render(row) {
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 => <DateTime date={date} /> render(date) {
return <DateTime date={date} />
},
}, },
{ {
title: t('user:row.menu'), title: t('user:row.menu'),
align: 'right', align: 'right',
render: row => { render(row) {
return ( return (
<Space> <Space>
<Link <Link href={'/admin/users/[id]'} as={`/admin/users/${row.id}`}>
href={'/admin/users/[id]'} <Button type={'primary'}>
as={`/admin/users/${row.id}`} <EditOutlined />
> </Button>
<Button type={'primary'}><EditOutlined /></Button>
</Link> </Link>
<Popconfirm <Popconfirm
@ -97,11 +108,13 @@ const Index: NextPage = () => {
okText={t('user:deleteNow')} okText={t('user:deleteNow')}
okButtonProps={{ danger: true }} okButtonProps={{ danger: true }}
> >
<Button danger><DeleteOutlined /></Button> <Button danger>
<DeleteOutlined />
</Button>
</Popconfirm> </Popconfirm>
</Space> </Space>
) )
} },
}, },
] ]
@ -109,9 +122,7 @@ const Index: NextPage = () => {
<Structure <Structure
title={t('admin:users')} title={t('admin:users')}
loading={loading} loading={loading}
breadcrumbs={[ breadcrumbs={[{ href: '/admin', name: t('admin:home') }]}
{ href: '/admin', name: t('admin:home') },
]}
padded={false} padded={false}
> >
<Table <Table
@ -119,7 +130,7 @@ const Index: NextPage = () => {
dataSource={entries} dataSource={entries}
rowKey={'id'} rowKey={'id'}
pagination={pagination} pagination={pagination}
onChange={next => { onChange={(next) => {
setPagination(next) setPagination(next)
refetch() refetch()
}} }}

Some files were not shown because too many files have changed in this diff Show More