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,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: { jsx: true }
ecmaFeatures: { jsx: true },
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
"plugin:jsx-a11y/recommended",
'plugin:jsx-a11y/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
],
rules: {
'prettier/prettier': [
"error", {}, { "usePrettierrc": true }
],
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
'react/prop-types': 'off',
'@typescript-eslint/no-empty-interface': 'off',
},
};
settings: {
react: {
version: 'detect',
},
},
}

View File

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

View File

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

View File

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

View File

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

View File

@ -7,13 +7,15 @@ interface Props {
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'
return (
<div style={{
display: 'inline-block'
}}>
<div
style={{
display: 'inline-block',
}}
>
{dayjs(props.date).format(format)}
</div>
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,86 +1,90 @@
import {PlusOutlined} from '@ant-design/icons/lib'
import {Button, Form, Select, Space, Tabs} from 'antd'
import {FormInstance} from 'antd/lib/form'
import {TabPaneProps} from 'antd/lib/tabs'
import React, {useCallback, useState} from 'react'
import {useTranslation} from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
import {FieldCard} from './field.card'
import {adminTypes} from './types'
import { PlusOutlined } from '@ant-design/icons/lib'
import { Button, Form, Select, Space, Tabs } from 'antd'
import { FormInstance } from 'antd/lib/form'
import { TabPaneProps } from 'antd/lib/tabs'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
import { FieldCard } from './field.card'
import { adminTypes } from './types'
interface Props extends TabPaneProps {
form: FormInstance
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 [nextType, setNextType] = useState('textfield')
const renderType = useCallback((field, index, remove) => {
return (
<FieldCard
form={props.form}
field={field}
index={index}
remove={remove}
fields={props.fields}
onChangeFields={props.onChangeFields}
/>
)
}, [props.fields])
const renderType = useCallback(
(field, index, remove) => {
return (
<FieldCard
form={props.form}
field={field}
index={index}
remove={remove}
fields={props.fields}
onChangeFields={props.onChangeFields}
/>
)
},
[props.fields]
)
const addField = useCallback((add, index) => {
return (
<Form.Item
wrapperCol={{span: 24}}
>
<Space
style={{
width: '100%',
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)
const addField = useCallback(
(add, index) => {
return (
<Form.Item wrapperCol={{ span: 24 }}>
<Space
style={{
width: '100%',
justifyContent: 'flex-end',
}}
>
<PlusOutlined /> Add Field
</Button>
</Space>
</Form.Item>
)
}, [props.fields, nextType])
<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
</Button>
</Space>
</Form.Item>
)
},
[props.fields, nextType]
)
return (
<Tabs.TabPane {...props}>
<Form.List
name={['form', 'fields']}
>
<Form.List name={['form', 'fields']}>
{(fields, { add, remove, move }) => {
const addAndMove = (index) => (defaults) => {
add(defaults)
@ -102,7 +106,6 @@ export const FieldsTab: React.FC<Props> = props => {
)
}}
</Form.List>
</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'
interface Props {
isLive: boolean
}
export const FormIsLive: React.FC<Props> = props => {
export const FormIsLive: React.FC<Props> = (props) => {
if (props.isLive) {
return (
<CheckCircleOutlined style={{
color: 'green'
}} />
<CheckCircleOutlined
style={{
color: 'green',
}}
/>
)
}
return (
<CloseCircleOutlined style={{
color: 'red'
}} />
<CloseCircleOutlined
style={{
color: 'red',
}}
/>
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import {DatePicker, Form} from 'antd'
import { DatePicker, Form } from 'antd'
import moment from 'moment'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {AdminFieldTypeProps} from './type.props'
import { useTranslation } from 'react-i18next'
import { AdminFieldTypeProps } from './type.props'
export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => {
export const DateType: React.FC<AdminFieldTypeProps> = ({ field }) => {
const { t } = useTranslation()
return (
@ -13,19 +13,17 @@ export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => {
label={t('type:date.default')}
name={[field.name, 'value']}
labelCol={{ span: 6 }}
getValueFromEvent={e => e ? e.format('YYYY-MM-DD') : undefined}
getValueProps={e => ({value: e ? moment(e) : undefined})}
getValueFromEvent={(e) => (e ? e.format('YYYY-MM-DD') : undefined)}
getValueProps={(e) => ({ value: e ? moment(e) : undefined })}
>
<DatePicker
format={'YYYY-MM-DD'}
/>
<DatePicker format={'YYYY-MM-DD'} />
</Form.Item>
<Form.Item
label={t('type:date.min')}
name={[field.name, 'optionKeys', 'min']}
labelCol={{ span: 6 }}
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 })}
>
<DatePicker />
</Form.Item>
@ -34,8 +32,8 @@ export const DateType: React.FC<AdminFieldTypeProps> = ({field, form}) => {
label={t('type:date.max')}
name={[field.name, 'optionKeys', 'max']}
labelCol={{ span: 6 }}
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 })}
>
<DatePicker />
</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 {useTranslation} from 'react-i18next'
import {AdminFieldTypeProps} from './type.props'
import { useTranslation } from 'react-i18next'
import { AdminFieldTypeProps } from './type.props'
export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
export const DropdownType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation()
return (
@ -16,11 +16,8 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
<Input />
</Form.Item>
<Form.List
name={[props.field.name, 'options']}
>
<Form.List name={[props.field.name, 'options']}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
@ -37,7 +34,7 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
<Form.Item
wrapperCol={{ span: 24 }}
name={[field.name, 'title']}
style={{marginBottom: 0}}
style={{ marginBottom: 0 }}
>
<Input placeholder={t('type:dropdown.titlePlaceholder')} />
</Form.Item>
@ -46,10 +43,8 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
<Form.Item
wrapperCol={{ span: 24 }}
name={[field.name, 'value']}
style={{marginBottom: 0}}
rules={[
{ required: true, message: t('validation:valueRequired') }
]}
style={{ marginBottom: 0 }}
rules={[{ required: true, message: t('validation:valueRequired') }]}
>
<Input placeholder={t('type:dropdown.valuePlaceholder')} />
</Form.Item>
@ -69,11 +64,9 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
}}
labelCol={{ span: 6 }}
>
<Button
type={'dashed'}
onClick={() => add()}
>
{t('type:dropdown.addOption')}</Button>
<Button type={'dashed'} onClick={() => add()}>
{t('type:dropdown.addOption')}
</Button>
</Form.Item>
</div>
)

View File

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

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd'
import { Form, Input } from 'antd'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {AdminFieldTypeProps} from './type.props'
import { useTranslation } from 'react-i18next'
import { AdminFieldTypeProps } from './type.props'
export const HiddenType: React.FC<AdminFieldTypeProps> = props => {
export const HiddenType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation()
return (

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd'
import { Form, Input } from 'antd'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {AdminFieldTypeProps} from './type.props'
import { useTranslation } from 'react-i18next'
import { AdminFieldTypeProps } from './type.props'
export const TextType: React.FC<AdminFieldTypeProps> = props => {
export const TextType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation()
return (

View File

@ -1,9 +1,9 @@
import {Form, Input} from 'antd'
import { Form, Input } from 'antd'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {AdminFieldTypeProps} from './type.props'
import { useTranslation } from 'react-i18next'
import { AdminFieldTypeProps } from './type.props'
export const TextareaType: React.FC<AdminFieldTypeProps> = props => {
export const TextareaType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation()
return (

View File

@ -1,6 +1,6 @@
import {FormInstance} from 'antd/lib/form'
import { FormInstance } from 'antd/lib/form'
export interface AdminFieldTypeProps {
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 {useTranslation} from 'react-i18next'
import {AdminFieldTypeProps} from './type.props'
import { useTranslation } from 'react-i18next'
import { AdminFieldTypeProps } from './type.props'
export const YesNoType: React.FC<AdminFieldTypeProps> = props => {
export const YesNoType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation()
// TODO add switch

View File

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

View File

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

View File

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

View File

@ -1,24 +1,32 @@
import {Form, Select} from 'antd'
import React, {useState} from 'react'
import {StyledSelect} from '../../styled/select'
import {FieldTypeProps} from './type.props'
import { Form, Select } from 'antd'
import React, { useState } from 'react'
import { StyledSelect } from '../../styled/select'
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)
return (
<div>
<Form.Item
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
rules={[{ required: field.required, message: 'Please provide Information' }]}
initialValue={field.value || null}
>
<StyledSelect design={design} 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
design={design}
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>
</Form.Item>
</div>

View File

@ -1,24 +1,20 @@
import {Form} from 'antd'
import { Form } from 'antd'
import React from 'react'
import {StyledInput} from '../../styled/input'
import {FieldTypeProps} from './type.props'
import { StyledInput } from '../../styled/input'
import { FieldTypeProps } from './type.props'
export const EmailType: React.FC<FieldTypeProps> = ({field, design}) => {
export const EmailType: React.FC<FieldTypeProps> = ({ field, design }) => {
return (
<div>
<Form.Item
name={[field.id, 'value']}
rules={[
{ 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}
>
<StyledInput
design={design}
allowClear
size={'large'}
/>
<StyledInput design={design} allowClear size={'large'} />
</Form.Item>
</div>
)

View File

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

View File

@ -1,24 +1,20 @@
import {Form} from 'antd'
import { Form } from 'antd'
import React from 'react'
import {StyledInput} from '../../styled/input'
import {FieldTypeProps} from './type.props'
import { StyledInput } from '../../styled/input'
import { FieldTypeProps } from './type.props'
export const LinkType: React.FC<FieldTypeProps> = ({field, design}) => {
export const LinkType: React.FC<FieldTypeProps> = ({ field, design }) => {
return (
<div>
<Form.Item
name={[field.id, 'value']}
rules={[
{ 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}
>
<StyledInput
design={design}
allowClear
size={'large'}
/>
<StyledInput design={design} allowClear size={'large'} />
</Form.Item>
</div>
)

View File

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

View File

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

View File

@ -1,17 +1,15 @@
import {Form, Rate} from 'antd'
import { Form, Rate } from 'antd'
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
return (
<div>
<Form.Item
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
rules={[{ required: field.required, message: 'Please provide Information' }]}
initialValue={parseFloat(field.value)}
>
<Rate allowHalf />

View File

@ -1,27 +1,19 @@
import {Form} from 'antd'
import { Form } from 'antd'
import React from 'react'
import {StyledInput} from '../../styled/input'
import {FieldTypeProps} from './type.props'
import { StyledInput } from '../../styled/input'
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
return (
<div>
<Form.Item
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' }
]}
rules={[{ required: field.required, message: 'Please provide Information' }]}
initialValue={field.value}
>
<StyledInput
design={design}
allowClear
size={'large'}
/>
<StyledInput design={design} allowClear size={'large'} />
</Form.Item>
</div>
)

View File

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

View File

@ -1,15 +1,13 @@
import {Form, Switch} from 'antd'
import { Form, Switch } from 'antd'
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 (
<div>
<Form.Item
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
rules={[{ required: field.required, message: 'Please provide Information' }]}
initialValue={field.value}
>
<Switch />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react'
import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
interface Props {
type: 'question' | 'answer'
@ -8,11 +8,12 @@ interface Props {
}
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}) => {
return (
<Header {...props}>{children}</Header>
)
export const StyledH1: React.FC<Props> = ({ children, ...props }) => {
return <Header {...props}>{children}</Header>
}

View File

@ -1,17 +1,18 @@
import React from 'react'
import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
interface Props {
type: 'question' | 'answer'
design: FormDesignFragment
}
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}) => {
return (
<Header {...props}>{children}</Header>
)
export const StyledH2: React.FC<Props> = ({ children, ...props }) => {
return <Header {...props}>{children}</Header>
}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react'
import styled from 'styled-components'
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
interface Props {
type: 'question' | 'answer'
@ -8,11 +8,12 @@ interface Props {
}
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}) => {
return (
<Paragraph {...props}>{children}</Paragraph>
)
export const StyledP: React.FC<Props> = ({ children, ...props }) => {
return <Paragraph {...props}>{children}</Paragraph>
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,37 +1,47 @@
import {useMutation} from '@apollo/react-hooks'
import {useCallback, useEffect, useState} from 'react'
import { useMutation } from '@apollo/react-hooks'
import { useCallback, useEffect, useState } from 'react'
import {
SUBMISSION_SET_FIELD_MUTATION,
SubmissionSetFieldMutationData,
SubmissionSetFieldMutationVariables
SubmissionSetFieldMutationVariables,
} from '../graphql/mutation/submission.set.field.mutation'
import {
SUBMISSION_START_MUTATION,
SubmissionStartMutationData,
SubmissionStartMutationVariables
SubmissionStartMutationVariables,
} from '../graphql/mutation/submission.start.mutation'
export const useSubmission = (formId: string) => {
const [submission, setSubmission] = useState<{ id: string, token: string }>()
interface Submission {
// 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)
const [save] = useMutation<SubmissionSetFieldMutationData, SubmissionSetFieldMutationVariables>(SUBMISSION_SET_FIELD_MUTATION)
export const useSubmission = (formId: string): Submission => {
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(() => {
(async () => {
;(async () => {
const token = [...Array(40)].map(() => Math.random().toString(36)[2]).join('')
const {data} = await start({
const { data } = await start({
variables: {
form: formId,
submission: {
token,
device: {
name: /Mobi/i.test(window.navigator.userAgent) ? 'mobile' : 'desktop',
type: window.navigator.userAgent
}
}
}
type: window.navigator.userAgent,
},
},
},
})
setSubmission({
@ -41,19 +51,23 @@ export const useSubmission = (formId: string) => {
})()
}, [formId])
const setField = useCallback(async (fieldId: string, data: any) => {
console.log('just save', fieldId, data)
await save({
variables: {
submission: submission.id,
field: {
token: submission.token,
field: fieldId,
data: JSON.stringify(data)
}
}
})
}, [submission])
const setField = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async (fieldId: string, data: any) => {
console.log('just save', fieldId, data)
await save({
variables: {
submission: submission.id,
field: {
token: submission.token,
field: fieldId,
data: JSON.stringify(data),
},
},
})
},
[submission]
)
const finish = useCallback(() => {
console.log('finish submission!!', formId)

View File

@ -1,29 +1,29 @@
import {useEffect, useState} from 'react'
import { useEffect, useState } from 'react'
export const useWindowSize = () => {
const isClient = typeof window === 'object';
export const useWindowSize = (): { width?: number; height?: number } => {
const isClient = typeof window === 'object'
function getSize() {
return {
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(() => {
if (!isClient) {
return;
return
}
function handleResize() {
setWindowSize(getSize());
setWindowSize(getSize())
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures that effect is only run on mount and unmount
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, []) // 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 {TabPaneProps} from 'antd/lib/tabs'
import { Form, Input, Select, Tabs } from 'antd'
import { TabPaneProps } from 'antd/lib/tabs'
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 (
<Tabs.TabPane {...props}>
<Form.Item
@ -45,7 +45,7 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
message: 'Please select a role',
},
]}
getValueFromEvent={e => {
getValueFromEvent={(e) => {
switch (e) {
case 'superuser':
return ['user', 'admin', 'superuser']
@ -55,7 +55,7 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
return ['user']
}
}}
getValueProps={v => {
getValueProps={(v) => {
let role = 'user'
if (v && v.includes('superuser')) {
@ -65,12 +65,16 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
}
return {
value: role
value: role,
}
}}
>
<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>
</Form.Item>
@ -85,24 +89,21 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
]}
>
<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>
</Form.Item>
<Form.Item
label="First Name"
name={['user', 'firstName']}
>
<Input />
<Form.Item label="First Name" name={['user', 'firstName']}>
<Input />
</Form.Item>
<Form.Item
label="Last Name"
name={['user', 'lastName']}
>
<Input />
<Form.Item label="Last Name" name={['user', 'lastName']}>
<Input />
</Form.Item>
</Tabs.TabPane>
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost'
import { gql } from 'apollo-boost'
export interface FormPageFragment {
show: boolean

View File

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

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost'
import {ADMIN_FORM_FRAGMENT, AdminFormFragment} from '../fragment/admin.form.fragment'
import { gql } from 'apollo-boost'
import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
export interface AdminFormCreateMutationData {
form: AdminFormFragment

View File

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

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost'
import {ADMIN_FORM_FRAGMENT, AdminFormFragment} from '../fragment/admin.form.fragment'
import { gql } from 'apollo-boost'
import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
export interface AdminFormUpdateMutationData {
form: AdminFormFragment

View File

@ -1,6 +1,6 @@
import {gql} from 'apollo-boost'
import {ADMIN_PROFILE_FRAGMENT} from '../fragment/admin.profile.fragment'
import {AdminUserFragment} from '../fragment/admin.user.fragment'
import { gql } from 'apollo-boost'
import { ADMIN_PROFILE_FRAGMENT } from '../fragment/admin.profile.fragment'
import { AdminUserFragment } from '../fragment/admin.user.fragment'
export interface AdminProfileUpdateMutationData {
user: AdminUserFragment

View File

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

View File

@ -1,5 +1,5 @@
import {gql} from 'apollo-boost'
import {ADMIN_USER_FRAGMENT, AdminUserFragment} from '../fragment/admin.user.fragment'
import { gql } from 'apollo-boost'
import { ADMIN_USER_FRAGMENT, AdminUserFragment } from '../fragment/admin.user.fragment'
export interface AdminUserUpdateMutationData {
user: AdminUserFragment

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost'
import { gql } from 'apollo-boost'
export const LOGIN_MUTATION = gql`
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`
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`
mutation start($submission: ID!,$field: SubmissionSetFieldInput!) {
mutation start($submission: ID!, $field: SubmissionSetFieldInput!) {
submission: submissionSetField(submission: $submission, field: $field) {
id
percentageComplete

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost'
import { gql } from 'apollo-boost'
export interface SubmissionSetFieldMutationData {
submission: {
@ -17,7 +17,7 @@ export interface SubmissionSetFieldMutationVariables {
}
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) {
id
percentageComplete

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost'
import { gql } from 'apollo-boost'
export interface AdminPagerFormEntryQueryData {
id: string
@ -30,7 +30,7 @@ export interface AdminPagerFormQueryVariables {
}
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) {
entries {
id

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost'
import { gql } from 'apollo-boost'
export interface AdminPagerSubmissionFormFieldQueryData {
title: string
@ -56,7 +56,7 @@ export interface AdminPagerSubmissionQueryVariables {
}
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) {
id
title

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost'
import { gql } from 'apollo-boost'
export interface AdminPagerUserEntryQueryData {
id: string
@ -24,7 +24,7 @@ export interface AdminPagerUserQueryVariables {
}
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) {
entries {
id

View File

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

View File

@ -1,4 +1,4 @@
import {gql} from 'apollo-boost'
import { gql } from 'apollo-boost'
export interface AdminStatisticQueryData {
forms: {
@ -12,8 +12,7 @@ export interface AdminStatisticQueryData {
}
}
export interface AdminStatisticQueryVariables {
}
export interface AdminStatisticQueryVariables {}
export const ADMIN_STATISTIC_QUERY = gql`
query {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,19 @@
import {useMutation} from '@apollo/react-hooks'
import {Button, Form, Input, message, Tabs} from 'antd'
import {useForm} from 'antd/lib/form/Form'
import {cleanInput} from 'components/clean.input'
import {BaseDataTab} from 'components/form/admin/base.data.tab'
import { useMutation } from '@apollo/react-hooks'
import { Button, Form, Input, message, Tabs } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { cleanInput } from 'components/clean.input'
import { BaseDataTab } from 'components/form/admin/base.data.tab'
import Structure from 'components/structure'
import {withAuth} from 'components/with.auth'
import {AdminFormQueryData} from 'graphql/query/admin.form.query'
import {NextPage} from 'next'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
import { withAuth } from 'components/with.auth'
import { AdminFormQueryData } from 'graphql/query/admin.form.query'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
ADMIN_FORM_CREATE_MUTATION,
AdminFormCreateMutationData,
AdminFormCreateMutationVariables
AdminFormCreateMutationVariables,
} from '../../../graphql/mutation/admin.form.create.mutation'
const Create: NextPage = () => {
@ -21,15 +21,19 @@ const Create: NextPage = () => {
const router = useRouter()
const [form] = useForm()
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) => {
setSaving(true)
try {
const next = (await create({
variables: cleanInput(formData),
})).data
const next = (
await create({
variables: cleanInput(formData),
})
).data
message.success(t('form:created'))
@ -52,18 +56,16 @@ const Create: NextPage = () => {
{ href: '/admin/forms', name: t('admin:forms') },
]}
extra={[
<Button
key={'create'}
onClick={form.submit}
type={'primary'}
>{t('form:createNow')}</Button>,
<Button key={'create'} onClick={form.submit} type={'primary'}>
{t('form:createNow')}
</Button>,
]}
style={{paddingTop: 0}}
style={{ paddingTop: 0 }}
>
<Form
form={form}
onFinish={save}
onFinishFailed={errors => {
onFinishFailed={() => {
// TODO process errors
message.error(t('validation:mandatoryFieldsMissing'))
}}
@ -76,7 +78,9 @@ const Create: NextPage = () => {
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>
<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 {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 {
DeleteOutlined,
EditOutlined,
GlobalOutlined,
UnorderedListOutlined,
} from '@ant-design/icons/lib'
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 {TimeAgo} from 'components/time.ago'
import {withAuth} from 'components/with.auth'
import { TimeAgo } from 'components/time.ago'
import { withAuth } from 'components/with.auth'
import {
ADMIN_PAGER_FORM_QUERY,
AdminPagerFormEntryQueryData,
AdminPagerFormQueryData,
AdminPagerFormQueryVariables
AdminPagerFormQueryVariables,
} from 'graphql/query/admin.pager.form.query'
import {NextPage} from 'next'
import { NextPage } from 'next'
import Link from 'next/link'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
ADMIN_FORM_DELETE_MUTATION,
AdminFormDeleteMutationData,
AdminFormDeleteMutationVariables
AdminFormDeleteMutationVariables,
} from '../../../graphql/mutation/admin.form.delete.mutation'
const Index: NextPage = () => {
@ -30,29 +35,35 @@ const Index: NextPage = () => {
pageSize: 25,
})
const [entries, setEntries] = useState<AdminPagerFormEntryQueryData[]>()
const {loading, refetch} = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(ADMIN_PAGER_FORM_QUERY, {
variables: {
limit: pagination.pageSize,
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0
},
onCompleted: ({pager}) => {
setPagination({
...pagination,
total: pager.total,
})
setEntries(pager.entries)
const { loading, refetch } = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(
ADMIN_PAGER_FORM_QUERY,
{
variables: {
limit: pagination.pageSize,
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
},
onCompleted: ({ pager }) => {
setPagination({
...pagination,
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) => {
try {
await executeDelete({
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) {
setPagination({ ...pagination, current: 1 })
} else {
@ -69,7 +80,9 @@ const Index: NextPage = () => {
{
title: t('form:row.isLive'),
dataIndex: 'isLive',
render: live => <FormIsLive isLive={live} />
render(live) {
return <FormIsLive isLive={live} />
},
},
{
title: t('form:row.title'),
@ -78,49 +91,55 @@ const Index: NextPage = () => {
{
title: t('form:row.admin'),
dataIndex: 'admin',
render: user => (
<Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}>
<Tooltip title={user.email}>
<Button type={'dashed'}>{user.username}</Button>
</Tooltip>
</Link>
)
render(user) {
return (
<Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}>
<Tooltip title={user.email}>
<Button type={'dashed'}>{user.username}</Button>
</Tooltip>
</Link>
)
},
},
{
title: t('form:row.language'),
dataIndex: 'language',
render: lang => t(`language:${lang}`)
render(lang) {
return t(`language:${lang}`)
},
},
{
title: t('form:row.created'),
dataIndex: 'created',
render: date => <DateTime date={date} />
render(date) {
return <DateTime date={date} />
},
},
{
title: t('form:row.lastModified'),
dataIndex: 'lastModified',
render: date => <TimeAgo date={date} />
render(date) {
return <TimeAgo date={date} />
},
},
{
title: t('form:row.menu'),
align: 'right',
render: row => {
render(row) {
return (
<Space>
<Link
href={'/admin/forms/[id]/submissions'}
as={`/admin/forms/${row.id}/submissions`}
>
<Link href={'/admin/forms/[id]/submissions'} as={`/admin/forms/${row.id}/submissions`}>
<Tooltip title={'Show Submissions'}>
<Button><UnorderedListOutlined /></Button>
<Button>
<UnorderedListOutlined />
</Button>
</Tooltip>
</Link>
<Link
href={'/admin/forms/[id]'}
as={`/admin/forms/${row.id}`}
>
<Button type={'primary'}><EditOutlined /></Button>
<Link href={'/admin/forms/[id]'} as={`/admin/forms/${row.id}`}>
<Button type={'primary'}>
<EditOutlined />
</Button>
</Link>
<Popconfirm
@ -129,20 +148,19 @@ const Index: NextPage = () => {
okText={t('form:deleteNow')}
okButtonProps={{ danger: true }}
>
<Button danger><DeleteOutlined /></Button>
<Button danger>
<DeleteOutlined />
</Button>
</Popconfirm>
<Tooltip title={row.isLive ? null : 'Not Public accessible!'}>
<Button
href={`/form/${row.id}`}
target={'_blank'}
>
<Button href={`/form/${row.id}`} target={'_blank'}>
<GlobalOutlined />
</Button>
</Tooltip>
</Space>
)
}
},
},
]
@ -151,18 +169,11 @@ const Index: NextPage = () => {
title={t('admin:forms')}
selected={'forms'}
loading={loading}
breadcrumbs={[
{ href: '/admin', name: t('admin:home') },
]}
breadcrumbs={[{ href: '/admin', name: t('admin:home') }]}
padded={false}
extra={[
<Link
key={'create'}
href={'/admin/forms/create'}
>
<Button
type={'primary'}
>{t('form:new')}</Button>
<Link key={'create'} href={'/admin/forms/create'}>
<Button type={'primary'}>{t('form:new')}</Button>
</Link>,
]}
>
@ -171,7 +182,7 @@ const Index: NextPage = () => {
dataSource={entries}
rowKey={'id'}
pagination={pagination}
onChange={next => {
onChange={(next) => {
setPagination(next)
refetch()
}}

View File

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

View File

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

View File

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

View File

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

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