mirror of
https://github.com/IT4Change/ohmyform-ui.git
synced 2025-12-13 09:45:50 +00:00
apply eslint
This commit is contained in:
parent
c2608f047e
commit
8d81390c83
16
.eslintrc.js
16
.eslintrc.js
@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -9,4 +9,3 @@ cache:
|
||||
script:
|
||||
- yarn
|
||||
- yarn lint
|
||||
- yarn build
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import {UpOutlined} from '@ant-design/icons/lib'
|
||||
import { useQuery } from '@apollo/react-hooks'
|
||||
import {Button, Menu, Select} from 'antd'
|
||||
import { Button, Select } from 'antd'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
@ -16,7 +15,7 @@ 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)
|
||||
@ -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 ? (
|
||||
[
|
||||
{props.me
|
||||
? [
|
||||
<span style={{ color: '#FFF' }} key={'user'}>
|
||||
Hi, {props.me.username}
|
||||
</span>,
|
||||
<Button
|
||||
key={'logout'}
|
||||
type={'link'}
|
||||
ghost
|
||||
onClick={logout}
|
||||
>
|
||||
<Button key={'logout'} type={'link'} ghost onClick={logout}>
|
||||
{t('logout')}
|
||||
</Button>
|
||||
</Button>,
|
||||
]
|
||||
): (
|
||||
[
|
||||
: [
|
||||
<Link href={'/login'} key={'login'}>
|
||||
<Button
|
||||
type={'link'}
|
||||
ghost
|
||||
>
|
||||
<Button type={'link'} ghost>
|
||||
{t('login')}
|
||||
</Button>
|
||||
</Link>,
|
||||
<Link href={'/register'} key={'register'}>
|
||||
<Button
|
||||
type={'link'}
|
||||
ghost
|
||||
disabled={data ? data.disabledSignUp.value : false}
|
||||
>
|
||||
<Button type={'link'} ghost disabled={data ? data.disabledSignUp.value : false}>
|
||||
{t('register')}
|
||||
</Button>
|
||||
</Link>
|
||||
]
|
||||
)}
|
||||
</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',
|
||||
}}
|
||||
>
|
||||
© OhMyForm
|
||||
|
||||
@ -5,13 +5,15 @@ 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={{
|
||||
<Layout
|
||||
style={{
|
||||
height: '100vh',
|
||||
background: '#437fdc'
|
||||
}}>
|
||||
background: '#437fdc',
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Layout>
|
||||
</Spin>
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -2,13 +2,15 @@ import React from 'react'
|
||||
|
||||
export const ErrorPage: React.FC = () => {
|
||||
return (
|
||||
<div style={{
|
||||
<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>
|
||||
|
||||
@ -4,7 +4,7 @@ import React from 'react'
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,15 +4,12 @@ import React from 'react'
|
||||
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>
|
||||
))}
|
||||
|
||||
@ -5,7 +5,7 @@ import React from 'react'
|
||||
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,9 +33,7 @@ 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>
|
||||
@ -53,40 +45,53 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
|
||||
label={index === 0 ? t('form:endPage.buttons') : ''}
|
||||
key={field.key}
|
||||
>
|
||||
<Card
|
||||
actions={[
|
||||
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
||||
]}
|
||||
>
|
||||
<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')}
|
||||
]}
|
||||
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 }}>
|
||||
<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 }}>
|
||||
<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 }}>
|
||||
<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 }}>
|
||||
<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 }}>
|
||||
<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%' }}
|
||||
>
|
||||
|
||||
@ -11,31 +11,27 @@ 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) => {
|
||||
onChangeFields(
|
||||
fields.map((field, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...field,
|
||||
@ -44,7 +40,8 @@ export const FieldCard: React.FC<Props> = props => {
|
||||
} 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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -11,14 +11,15 @@ 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) => {
|
||||
const renderType = useCallback(
|
||||
(field, index, remove) => {
|
||||
return (
|
||||
<FieldCard
|
||||
form={props.form}
|
||||
@ -29,21 +30,26 @@ export const FieldsTab: React.FC<Props> = props => {
|
||||
onChangeFields={props.onChangeFields}
|
||||
/>
|
||||
)
|
||||
}, [props.fields])
|
||||
},
|
||||
[props.fields]
|
||||
)
|
||||
|
||||
const addField = useCallback((add, index) => {
|
||||
const addField = useCallback(
|
||||
(add, index) => {
|
||||
return (
|
||||
<Form.Item
|
||||
wrapperCol={{span: 24}}
|
||||
>
|
||||
<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 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"
|
||||
@ -58,7 +64,7 @@ export const FieldsTab: React.FC<Props> = props => {
|
||||
title: '',
|
||||
description: '',
|
||||
required: false,
|
||||
value: ''
|
||||
value: '',
|
||||
}
|
||||
|
||||
add(defaults)
|
||||
@ -72,15 +78,13 @@ export const FieldsTab: React.FC<Props> = props => {
|
||||
</Space>
|
||||
</Form.Item>
|
||||
)
|
||||
}, [props.fields, nextType])
|
||||
|
||||
},
|
||||
[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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,18 +5,22 @@ 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',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ interface Props extends TabPaneProps {
|
||||
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>
|
||||
))}
|
||||
|
||||
@ -11,7 +11,7 @@ interface Props extends TabPaneProps {
|
||||
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>
|
||||
))}
|
||||
|
||||
@ -5,7 +5,7 @@ import React from 'react'
|
||||
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,9 +33,7 @@ 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>
|
||||
@ -53,40 +45,53 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
|
||||
label={index === 0 ? t('form:startPage.buttons') : ''}
|
||||
key={field.key}
|
||||
>
|
||||
<Card
|
||||
actions={[
|
||||
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
||||
]}
|
||||
>
|
||||
<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')}
|
||||
]}
|
||||
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 }}>
|
||||
<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 }}>
|
||||
<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 }}>
|
||||
<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 }}>
|
||||
<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 }}>
|
||||
<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%' }}
|
||||
>
|
||||
|
||||
@ -5,7 +5,7 @@ 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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import React from 'react'
|
||||
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>
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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) => (
|
||||
@ -47,9 +44,7 @@ export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
|
||||
wrapperCol={{ span: 24 }}
|
||||
name={[field.name, 'value']}
|
||||
style={{ marginBottom: 0 }}
|
||||
rules={[
|
||||
{ required: true, message: t('validation:valueRequired') }
|
||||
]}
|
||||
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>
|
||||
)
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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'} />
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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 (
|
||||
|
||||
@ -15,15 +15,15 @@ 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,
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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'} />
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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 (
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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) => (
|
||||
@ -47,9 +44,7 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
|
||||
wrapperCol={{ span: 24 }}
|
||||
name={[field.name, 'value']}
|
||||
style={{ marginBottom: 0 }}
|
||||
rules={[
|
||||
{ required: true, message: t('validation:valueRequired') }
|
||||
]}
|
||||
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>
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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>
|
||||
)
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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 (
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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 (
|
||||
|
||||
@ -2,5 +2,5 @@ import {FormInstance} from 'antd/lib/form'
|
||||
|
||||
export interface AdminFieldTypeProps {
|
||||
form: FormInstance
|
||||
field: any
|
||||
field: { name: string }
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react'
|
||||
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
|
||||
|
||||
@ -13,12 +13,13 @@ 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,31 +45,40 @@ export const Field: React.FC<Props> = ({field, save, design, children, next, pre
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
<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>}
|
||||
}}
|
||||
>
|
||||
<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={{
|
||||
<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 }} />
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -10,34 +10,45 @@ interface Props {
|
||||
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={{
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}} {...props}>
|
||||
<div style={{
|
||||
}}
|
||||
{...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>
|
||||
}}
|
||||
>
|
||||
<StyledH1 design={design} type={'question'}>
|
||||
{page.title}
|
||||
</StyledH1>
|
||||
<StyledP design={design} type={'question'}>
|
||||
{page.paragraph}
|
||||
</StyledP>
|
||||
</div>
|
||||
<div style={{
|
||||
<div
|
||||
style={{
|
||||
padding: 32,
|
||||
display: 'flex',
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{page.buttons.length > 0 && (
|
||||
<Space>
|
||||
{page.buttons.map((button, key) => {
|
||||
@ -49,7 +60,10 @@ 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>
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Form } from 'antd'
|
||||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import moment from 'moment'
|
||||
import moment, { Moment } from 'moment'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { StyledDateInput } from '../../styled/date.input'
|
||||
import { FieldTypeProps } from './type.props'
|
||||
@ -10,7 +10,7 @@ export const DateType: React.FC<FieldTypeProps> = ({ field, design}) => {
|
||||
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
|
||||
|
||||
@ -10,14 +10,22 @@ export const DropdownType: React.FC<FieldTypeProps> = ({field, design}) => {
|
||||
<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>
|
||||
|
||||
@ -10,15 +10,11 @@ export const EmailType: React.FC<FieldTypeProps> = ({field, design}) => {
|
||||
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>
|
||||
)
|
||||
|
||||
@ -14,14 +14,14 @@ 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,
|
||||
}
|
||||
|
||||
@ -10,15 +10,11 @@ export const LinkType: React.FC<FieldTypeProps> = ({field, design}) => {
|
||||
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>
|
||||
)
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -8,18 +8,16 @@ export const RadioType: React.FC<FieldTypeProps> = ({field, design}) => {
|
||||
<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}
|
||||
>
|
||||
{field.options
|
||||
.filter((option) => option.key === null)
|
||||
.map((option) => (
|
||||
<StyledRadio design={design} value={option.value} key={option.value}>
|
||||
{option.title || option.value}
|
||||
</StyledRadio>
|
||||
))}
|
||||
|
||||
@ -9,9 +9,7 @@ export const RatingType: React.FC<FieldTypeProps> = ({field}) => {
|
||||
<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 />
|
||||
|
||||
@ -6,22 +6,14 @@ import {FieldTypeProps} from './type.props'
|
||||
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>
|
||||
)
|
||||
|
||||
@ -8,16 +8,10 @@ export const TextareaType: React.FC<FieldTypeProps> = ({field, design}) => {
|
||||
<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>
|
||||
)
|
||||
|
||||
@ -7,9 +7,7 @@ export const YesNoType: React.FC<FieldTypeProps> = ({field}) => {
|
||||
<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 />
|
||||
|
||||
@ -3,10 +3,10 @@ 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,
|
||||
|
||||
@ -5,15 +5,17 @@ interface Props {
|
||||
message?: string
|
||||
}
|
||||
|
||||
export const LoadingPage: React.FC<Props> = props => {
|
||||
export const LoadingPage: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<div style={{
|
||||
<div
|
||||
style={{
|
||||
height: '100vh',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
{props.message}
|
||||
</div>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -3,7 +3,7 @@ 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 React, { CSSProperties, FunctionComponent } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { languages } from '../i18n'
|
||||
import { sideMenu, SideMenuElement } from './sidemenu'
|
||||
@ -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,23 +62,26 @@ const Structure: FunctionComponent<Props> = (props) => {
|
||||
}, [props.selected])
|
||||
|
||||
const buildMenu = (data: SideMenuElement[]): JSX.Element[] => {
|
||||
return data.map((element): JSX.Element => {
|
||||
return data.map(
|
||||
(element): JSX.Element => {
|
||||
if (element.items && element.items.length > 0) {
|
||||
if (element.group) {
|
||||
return (
|
||||
<ItemGroup
|
||||
key={element.key}
|
||||
title={(
|
||||
<div style={{
|
||||
title={
|
||||
<div
|
||||
style={{
|
||||
textTransform: 'uppercase',
|
||||
paddingTop: 16,
|
||||
fontWeight: 'bold',
|
||||
color: '#444'
|
||||
}}>
|
||||
color: '#444',
|
||||
}}
|
||||
>
|
||||
{element.icon}
|
||||
{element.name}
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
>
|
||||
{buildMenu(element.items)}
|
||||
</ItemGroup>
|
||||
@ -114,7 +116,8 @@ const Structure: FunctionComponent<Props> = (props) => {
|
||||
{element.name}
|
||||
</Menu.Item>
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const signOut = async (): Promise<void> => {
|
||||
@ -129,46 +132,57 @@ const Structure: FunctionComponent<Props> = (props) => {
|
||||
paddingLeft: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
<div
|
||||
style={{
|
||||
float: 'left',
|
||||
color: '#FFF',
|
||||
fontSize: 14,
|
||||
marginRight: 26,
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
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%' }}>
|
||||
<Dropdown
|
||||
overlay={(
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item onClick={() => router.push('/admin/profile')}>Profile</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item onClick={signOut}>Logout</Menu.Item>
|
||||
</Menu>
|
||||
)}
|
||||
}
|
||||
onVisibleChange={setUserMenu}
|
||||
visible={userMenu}
|
||||
>
|
||||
<a style={{
|
||||
<div
|
||||
style={{
|
||||
color: '#FFF',
|
||||
alignItems: 'center',
|
||||
display: 'inline-flex',
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<UserOutlined style={{ fontSize: 24 }} />
|
||||
<CaretDownOutlined />
|
||||
</a>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Header>
|
||||
<Layout style={{
|
||||
<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}
|
||||
|
||||
@ -5,25 +5,23 @@ import styled from 'styled-components'
|
||||
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>
|
||||
)
|
||||
return <Styled {...props}>{children}</Styled>
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -9,8 +9,8 @@ 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>
|
||||
)
|
||||
return <Field {...props}>{children}</Field>
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
return <Header {...props}>{children}</Header>
|
||||
}
|
||||
|
||||
@ -7,11 +7,12 @@ interface Props {
|
||||
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>
|
||||
)
|
||||
return <Header {...props}>{children}</Header>
|
||||
}
|
||||
|
||||
@ -10,8 +10,8 @@ interface Props extends InputProps{
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
return <Field {...props}>{children}</Field>
|
||||
}
|
||||
|
||||
@ -10,8 +10,8 @@ interface Props extends InputNumberProps {
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
return <Field {...props}>{children}</Field>
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
return <Paragraph {...props}>{children}</Paragraph>
|
||||
}
|
||||
|
||||
@ -9,31 +9,29 @@ interface Props extends RadioProps {
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
return <Field {...props}>{children}</Field>
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
return <Field {...props}>{children}</Field>
|
||||
}
|
||||
|
||||
@ -10,8 +10,8 @@ interface Props extends TextAreaProps {
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
return <Field {...props}>{children}</Field>
|
||||
}
|
||||
|
||||
@ -9,13 +9,15 @@ 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>
|
||||
</Tooltip>
|
||||
|
||||
@ -3,22 +3,32 @@ 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({
|
||||
@ -28,10 +38,10 @@ export const useSubmission = (formId: string) => {
|
||||
token,
|
||||
device: {
|
||||
name: /Mobi/i.test(window.navigator.userAgent) ? 'mobile' : 'desktop',
|
||||
type: window.navigator.userAgent
|
||||
}
|
||||
}
|
||||
}
|
||||
type: window.navigator.userAgent,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
setSubmission({
|
||||
@ -41,7 +51,9 @@ export const useSubmission = (formId: string) => {
|
||||
})()
|
||||
}, [formId])
|
||||
|
||||
const setField = useCallback(async (fieldId: string, data: any) => {
|
||||
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: {
|
||||
@ -49,11 +61,13 @@ export const useSubmission = (formId: string) => {
|
||||
field: {
|
||||
token: submission.token,
|
||||
field: fieldId,
|
||||
data: JSON.stringify(data)
|
||||
}
|
||||
}
|
||||
data: JSON.stringify(data),
|
||||
},
|
||||
},
|
||||
})
|
||||
}, [submission])
|
||||
},
|
||||
[submission]
|
||||
)
|
||||
|
||||
const finish = useCallback(() => {
|
||||
console.log('finish submission!!', formId)
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
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
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import {TabPaneProps} from 'antd/lib/tabs'
|
||||
import React from 'react'
|
||||
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']}
|
||||
>
|
||||
<Form.Item label="First Name" name={['user', 'firstName']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Last Name"
|
||||
name={['user', 'lastName']}
|
||||
>
|
||||
<Form.Item label="Last Name" name={['user', 'lastName']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
</Tabs.TabPane>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import {Tag} from "antd"
|
||||
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>
|
||||
)
|
||||
|
||||
@ -6,14 +6,14 @@ 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,8 +37,10 @@ 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)
|
||||
@ -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} />
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,7 @@ export interface AdminProfileQueryData {
|
||||
user: AdminProfileFragment
|
||||
}
|
||||
|
||||
export interface AdminProfileQueryVariables {
|
||||
}
|
||||
export interface AdminProfileQueryVariables {}
|
||||
|
||||
export const ADMIN_PROFILE_QUERY = gql`
|
||||
query profile {
|
||||
|
||||
@ -12,8 +12,7 @@ export interface AdminStatisticQueryData {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AdminStatisticQueryVariables {
|
||||
}
|
||||
export interface AdminStatisticQueryVariables {}
|
||||
|
||||
export const ADMIN_STATISTIC_QUERY = gql`
|
||||
query {
|
||||
|
||||
@ -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 }) => {
|
||||
|
||||
@ -13,15 +13,18 @@ import Structure from 'components/structure'
|
||||
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 {
|
||||
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'
|
||||
@ -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,21 +59,26 @@ const Index: NextPage = () => {
|
||||
return next
|
||||
}
|
||||
|
||||
const {data, loading, error} = useQuery<AdminFormQueryData, AdminFormQueryVariables>(ADMIN_FORM_QUERY, {
|
||||
const { data, loading } = useQuery<AdminFormQueryData, AdminFormQueryVariables>(
|
||||
ADMIN_FORM_QUERY,
|
||||
{
|
||||
variables: {
|
||||
id: router.query.id as string
|
||||
id: router.query.id as string,
|
||||
},
|
||||
onCompleted: next => {
|
||||
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}) => {
|
||||
formData.form.fields = formData.form.fields
|
||||
.filter((e) => e && e.type)
|
||||
.map(({ optionKeys, ...field }) => {
|
||||
const options = field.options
|
||||
|
||||
if (optionKeys) {
|
||||
@ -86,14 +96,18 @@ const Index: NextPage = () => {
|
||||
|
||||
return {
|
||||
...field,
|
||||
options
|
||||
options,
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
const next = processNext((await update({
|
||||
const next = processNext(
|
||||
(
|
||||
await update({
|
||||
variables: cleanInput(formData),
|
||||
})).data)
|
||||
})
|
||||
).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 }}
|
||||
>
|
||||
<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
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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'
|
||||
@ -18,7 +19,7 @@ import {
|
||||
AdminPagerSubmissionEntryQueryData,
|
||||
AdminPagerSubmissionFormQueryData,
|
||||
AdminPagerSubmissionQueryData,
|
||||
AdminPagerSubmissionQueryVariables
|
||||
AdminPagerSubmissionQueryVariables,
|
||||
} from '../../../../graphql/query/admin.pager.submission.query'
|
||||
|
||||
const Submissions: NextPage = () => {
|
||||
@ -29,11 +30,14 @@ 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 }) => {
|
||||
setPagination({
|
||||
@ -42,35 +46,40 @@ const Submissions: NextPage = () => {
|
||||
})
|
||||
setForm(form)
|
||||
setEntries(pager.entries)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const columns: ColumnsType<AdminPagerSubmissionEntryQueryData> = [
|
||||
{
|
||||
title: t('submission:progress'),
|
||||
render: (row: AdminPagerSubmissionEntryQueryData) => {
|
||||
let status: any = 'active'
|
||||
render(row: AdminPagerSubmissionEntryQueryData) {
|
||||
const props: ProgressProps = {
|
||||
status: 'active',
|
||||
percent: Math.round(row.percentageComplete * 100),
|
||||
}
|
||||
|
||||
if (row.percentageComplete >= 1) {
|
||||
status = 'success'
|
||||
props.status = 'success'
|
||||
} else if (dayjs().diff(dayjs(row.lastModified), 'hour') > 4) {
|
||||
status = 'exception'
|
||||
props.status = 'exception'
|
||||
}
|
||||
|
||||
return (
|
||||
<Progress percent={Math.round(row.percentageComplete * 100)} status={status} />
|
||||
)
|
||||
}
|
||||
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()
|
||||
}}
|
||||
|
||||
@ -13,7 +13,7 @@ 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({
|
||||
const next = (
|
||||
await create({
|
||||
variables: cleanInput(formData),
|
||||
})).data
|
||||
})
|
||||
).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 }}
|
||||
>
|
||||
<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')} />
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import {DeleteOutlined, EditOutlined, GlobalOutlined, UnorderedListOutlined} from '@ant-design/icons/lib'
|
||||
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'
|
||||
@ -12,7 +17,7 @@ import {
|
||||
ADMIN_PAGER_FORM_QUERY,
|
||||
AdminPagerFormEntryQueryData,
|
||||
AdminPagerFormQueryData,
|
||||
AdminPagerFormQueryVariables
|
||||
AdminPagerFormQueryVariables,
|
||||
} from 'graphql/query/admin.pager.form.query'
|
||||
import { NextPage } from 'next'
|
||||
import Link from 'next/link'
|
||||
@ -21,7 +26,7 @@ import {useTranslation} from 'react-i18next'
|
||||
import {
|
||||
ADMIN_FORM_DELETE_MUTATION,
|
||||
AdminFormDeleteMutationData,
|
||||
AdminFormDeleteMutationVariables
|
||||
AdminFormDeleteMutationVariables,
|
||||
} from '../../../graphql/mutation/admin.form.delete.mutation'
|
||||
|
||||
const Index: NextPage = () => {
|
||||
@ -30,10 +35,12 @@ const Index: NextPage = () => {
|
||||
pageSize: 25,
|
||||
})
|
||||
const [entries, setEntries] = useState<AdminPagerFormEntryQueryData[]>()
|
||||
const {loading, refetch} = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(ADMIN_PAGER_FORM_QUERY, {
|
||||
const { loading, refetch } = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(
|
||||
ADMIN_PAGER_FORM_QUERY,
|
||||
{
|
||||
variables: {
|
||||
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 }) => {
|
||||
setPagination({
|
||||
@ -41,18 +48,22 @@ const Index: NextPage = () => {
|
||||
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,7 +91,8 @@ const Index: NextPage = () => {
|
||||
{
|
||||
title: t('form:row.admin'),
|
||||
dataIndex: 'admin',
|
||||
render: user => (
|
||||
render(user) {
|
||||
return (
|
||||
<Link href={'/admin/users/[id]'} as={`/admin/users/${user.id}`}>
|
||||
<Tooltip title={user.email}>
|
||||
<Button type={'dashed'}>{user.username}</Button>
|
||||
@ -86,41 +100,46 @@ const Index: NextPage = () => {
|
||||
</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()
|
||||
}}
|
||||
|
||||
@ -8,19 +8,17 @@ 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>
|
||||
|
||||
@ -2,7 +2,6 @@ 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'
|
||||
@ -10,37 +9,43 @@ 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'
|
||||
|
||||
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 => {
|
||||
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({
|
||||
const next = (
|
||||
await update({
|
||||
variables: cleanInput(formData),
|
||||
})).data
|
||||
})
|
||||
).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,21 +131,19 @@ 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']}
|
||||
>
|
||||
<Form.Item label={t('profile:firstName')} name={['user', 'firstName']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('profile:lastName')}
|
||||
name={['user', 'lastName']}
|
||||
>
|
||||
<Form.Item label={t('profile:lastName')} name={['user', 'lastName']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
@ -12,9 +12,13 @@ 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, {
|
||||
const { data, loading } = useQuery<AdminUserQueryData, AdminUserQueryVariables>(
|
||||
ADMIN_USER_QUERY,
|
||||
{
|
||||
variables: {
|
||||
id: router.query.id as string
|
||||
id: router.query.id as string,
|
||||
},
|
||||
onCompleted: next => {
|
||||
onCompleted: (next) => {
|
||||
form.setFieldsValue(next)
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const [update] = useMutation<AdminUserUpdateMutationData, AdminUserUpdateMutationVariables>(ADMIN_USER_UPDATE_MUTATION)
|
||||
const [update] = useMutation<AdminUserUpdateMutationData, AdminUserUpdateMutationVariables>(
|
||||
ADMIN_USER_UPDATE_MUTATION
|
||||
)
|
||||
|
||||
const save = async (formData: AdminUserQueryData) => {
|
||||
setSaving(true)
|
||||
try {
|
||||
const next = (await update({
|
||||
const next = (
|
||||
await update({
|
||||
variables: cleanInput(formData),
|
||||
})).data
|
||||
})
|
||||
).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 }}
|
||||
>
|
||||
<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>
|
||||
|
||||
@ -14,13 +14,13 @@ 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,10 +29,12 @@ const Index: NextPage = () => {
|
||||
pageSize: 10,
|
||||
})
|
||||
const [entries, setEntries] = useState<AdminPagerUserEntryQueryData[]>()
|
||||
const {loading, refetch} = useQuery<AdminPagerUserQueryData, AdminPagerUserQueryVariables>(ADMIN_PAGER_USER_QUERY, {
|
||||
const { loading, refetch } = useQuery<AdminPagerUserQueryData, AdminPagerUserQueryVariables>(
|
||||
ADMIN_PAGER_USER_QUERY,
|
||||
{
|
||||
variables: {
|
||||
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 }) => {
|
||||
setPagination({
|
||||
@ -40,18 +42,22 @@ const Index: NextPage = () => {
|
||||
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()
|
||||
}}
|
||||
|
||||
@ -28,7 +28,7 @@ const Index: NextPage<Props> = () => {
|
||||
const { loading, data, error } = useQuery<FormQueryData, FormQueryVariables>(FORM_QUERY, {
|
||||
variables: {
|
||||
id,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@ -43,15 +43,11 @@ const Index: NextPage<Props> = () => {
|
||||
}, [data])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<LoadingPage message={t('form:build')} />
|
||||
)
|
||||
return <LoadingPage message={t('form:build')} />
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage/>
|
||||
)
|
||||
return <ErrorPage />
|
||||
}
|
||||
|
||||
const design = data.form.design
|
||||
@ -74,19 +70,23 @@ const Index: NextPage<Props> = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
<div
|
||||
style={{
|
||||
background: design.colors.backgroundColor,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Swiper {...swiperConfig}>
|
||||
{[
|
||||
data.form.startPage.show ? <FormPage
|
||||
data.form.startPage.show ? (
|
||||
<FormPage
|
||||
key={'start'}
|
||||
type={'start'}
|
||||
page={data.form.startPage}
|
||||
design={design}
|
||||
next={goNext}
|
||||
prev={goPrev}
|
||||
/> : undefined,
|
||||
/>
|
||||
) : undefined,
|
||||
...data.form.fields
|
||||
.map((field, i) => {
|
||||
if (field.type === 'hidden') {
|
||||
@ -98,7 +98,7 @@ const Index: NextPage<Props> = () => {
|
||||
key={field.id}
|
||||
field={field}
|
||||
design={design}
|
||||
save={values => {
|
||||
save={(values) => {
|
||||
submission.setField(field.id, values[field.id])
|
||||
|
||||
if (data.form.fields.length === i + 1) {
|
||||
@ -116,8 +116,8 @@ const Index: NextPage<Props> = () => {
|
||||
okText: t('from:restart'),
|
||||
onOk: () => {
|
||||
window.location.reload()
|
||||
}
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,16 +127,18 @@ const Index: NextPage<Props> = () => {
|
||||
/>
|
||||
)
|
||||
})
|
||||
.filter(e => e !== null),
|
||||
data.form.endPage.show ? <FormPage
|
||||
.filter((e) => e !== null),
|
||||
data.form.endPage.show ? (
|
||||
<FormPage
|
||||
key={'end'}
|
||||
type={'end'}
|
||||
page={data.form.endPage}
|
||||
design={design}
|
||||
next={submission.finish}
|
||||
prev={goPrev}
|
||||
/> : undefined
|
||||
].filter(e => !!e)}
|
||||
/>
|
||||
) : undefined,
|
||||
].filter((e) => !!e)}
|
||||
</Swiper>
|
||||
</div>
|
||||
)
|
||||
@ -144,7 +146,7 @@ const Index: NextPage<Props> = () => {
|
||||
|
||||
Index.getInitialProps = async ({ query }) => {
|
||||
return {
|
||||
id: query.id as string
|
||||
id: query.id as string,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,6 @@ import {NextPage} from 'next'
|
||||
import React from 'react'
|
||||
|
||||
const Index: NextPage = () => {
|
||||
return (
|
||||
<ErrorPage />
|
||||
)
|
||||
return <ErrorPage />
|
||||
}
|
||||
export default Index
|
||||
|
||||
@ -16,13 +16,9 @@ const Index: NextPage = () => {
|
||||
useEffect(() => {
|
||||
if (router.pathname !== window.location.pathname) {
|
||||
let href = router.asPath
|
||||
let as = router.asPath;
|
||||
const as = router.asPath
|
||||
|
||||
[
|
||||
/(\/form\/)[^\/]+/i,
|
||||
/(\/admin\/forms\/)[^\/]+/i,
|
||||
/(\/admin\/users\/)[^\/]+/i,
|
||||
].forEach(r => {
|
||||
;[/(\/form\/)[^/]+/i, /(\/admin\/forms\/)[^/]+/i, /(\/admin\/users\/)[^/]+/i].forEach((r) => {
|
||||
if (r.test(as)) {
|
||||
href = href.replace(r, '$1[id]')
|
||||
}
|
||||
@ -32,18 +28,22 @@ const Index: NextPage = () => {
|
||||
}
|
||||
})
|
||||
|
||||
if (publicRuntimeConfig.spa || (process.browser && router.pathname !== window.location.pathname)) {
|
||||
return (
|
||||
<LoadingPage message={t('loading')} />
|
||||
)
|
||||
if (
|
||||
publicRuntimeConfig.spa ||
|
||||
(process.browser && router.pathname !== window.location.pathname)
|
||||
) {
|
||||
return <LoadingPage message={t('loading')} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout style={{
|
||||
<Layout
|
||||
style={{
|
||||
height: '100vh',
|
||||
background: '#437fdc'
|
||||
}}>
|
||||
background: '#437fdc',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={'OhMyForm'}
|
||||
style={{
|
||||
margin: 'auto',
|
||||
maxWidth: '90%',
|
||||
|
||||
@ -25,10 +25,7 @@ const Index: NextPage = () => {
|
||||
variables: data,
|
||||
})
|
||||
|
||||
await setAuth(
|
||||
result.data.tokens.access,
|
||||
result.data.tokens.refresh
|
||||
)
|
||||
await setAuth(result.data.tokens.access, result.data.tokens.refresh)
|
||||
|
||||
message.success(t('login:welcomeBack'))
|
||||
|
||||
@ -73,29 +70,18 @@ const Index: NextPage = () => {
|
||||
name="username"
|
||||
rules={[{ required: true, message: t('validation:usernameRequired') }]}
|
||||
>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder={t('login:usernamePlaceholder')}
|
||||
/>
|
||||
<Input size="large" placeholder={t('login:usernamePlaceholder')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
rules={[{ required: true, message: t('validation:passwordRequired') }]}
|
||||
>
|
||||
<Input.Password
|
||||
size="large"
|
||||
placeholder={t('login:passwordPlaceholder')}
|
||||
/>
|
||||
<Input.Password size="large" placeholder={t('login:passwordPlaceholder')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
block
|
||||
>
|
||||
<Button size="large" type="primary" htmlType="submit" block>
|
||||
{t('login:loginNow')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
@ -108,18 +94,12 @@ const Index: NextPage = () => {
|
||||
}}
|
||||
>
|
||||
<Link href={'/register'}>
|
||||
<Button
|
||||
type={'link'}
|
||||
ghost
|
||||
>
|
||||
<Button type={'link'} ghost>
|
||||
{t('register')}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={'/login/recover'}>
|
||||
<Button
|
||||
type={'link'}
|
||||
ghost
|
||||
>
|
||||
<Button type={'link'} ghost>
|
||||
{t('recover')}
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -28,14 +28,11 @@ const Register: NextPage = () => {
|
||||
try {
|
||||
const result = await register({
|
||||
variables: {
|
||||
user: data
|
||||
user: data,
|
||||
},
|
||||
})
|
||||
|
||||
await setAuth(
|
||||
result.data.tokens.access,
|
||||
result.data.tokens.refresh
|
||||
)
|
||||
await setAuth(result.data.tokens.access, result.data.tokens.refresh)
|
||||
|
||||
message.success(t('register:welcome'))
|
||||
|
||||
@ -51,9 +48,7 @@ const Register: NextPage = () => {
|
||||
}
|
||||
|
||||
if (data && data.disabledSignUp.value) {
|
||||
return (
|
||||
<ErrorPage />
|
||||
)
|
||||
return <ErrorPage />
|
||||
}
|
||||
|
||||
return (
|
||||
@ -85,23 +80,17 @@ const Register: NextPage = () => {
|
||||
name="username"
|
||||
rules={[{ required: true, message: t('validation:usernameRequired') }]}
|
||||
>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder={t('login:usernamePlaceholder')}
|
||||
/>
|
||||
<Input size="large" placeholder={t('login:usernamePlaceholder')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="email"
|
||||
rules={[
|
||||
{ required: true, message: t('validation:emailRequired') },
|
||||
{ type: 'email', message: t('validation:invalidEmail') }
|
||||
{ type: 'email', message: t('validation:invalidEmail') },
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder="Email"
|
||||
/>
|
||||
<Input size="large" placeholder="Email" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
@ -111,19 +100,11 @@ const Register: NextPage = () => {
|
||||
{ min: 5, message: t('validation:passwordMinLength') },
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
size="large"
|
||||
placeholder={t('login:passwordPlaceholder')}
|
||||
/>
|
||||
<Input.Password size="large" placeholder={t('login:passwordPlaceholder')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
block
|
||||
>
|
||||
<Button size="large" type="primary" htmlType="submit" block>
|
||||
{t('register:registerNow')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
@ -136,10 +117,7 @@ const Register: NextPage = () => {
|
||||
}}
|
||||
>
|
||||
<Link href={'/login'}>
|
||||
<Button
|
||||
type={'link'}
|
||||
ghost
|
||||
>
|
||||
<Button type={'link'} ghost>
|
||||
{t('register:gotoLogin')}
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -2,21 +2,19 @@ import redux, {Reducer} from 'redux'
|
||||
|
||||
export interface AuthState {
|
||||
authenticated?: boolean
|
||||
|
||||
}
|
||||
|
||||
type ActionTypes = 'AUTH_INIT' | 'AUTH_LOGOUT' | 'AUTH_UPDATE_SETTINGS';
|
||||
type ActionTypes = 'AUTH_INIT' | 'AUTH_LOGOUT' | 'AUTH_UPDATE_SETTINGS'
|
||||
type Action = redux.Action<ActionTypes> & redux.AnyAction
|
||||
|
||||
export const actionTypes: { [key: string]: ActionTypes } = {
|
||||
INIT: 'AUTH_INIT',
|
||||
LOGOUT: 'AUTH_LOGOUT',
|
||||
UPDATE_SETTINGS: 'AUTH_UPDATE_SETTINGS',
|
||||
};
|
||||
|
||||
const initialState: AuthState = {
|
||||
}
|
||||
|
||||
export const auth: Reducer<AuthState, Action> = (state = initialState, action: Action): AuthState => {
|
||||
const initialState: AuthState = {}
|
||||
|
||||
export const auth: Reducer<AuthState, Action> = (state = initialState): AuthState => {
|
||||
return state
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import {composeWithDevTools} from 'redux-devtools-extension'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import { auth, AuthState } from './auth'
|
||||
|
||||
|
||||
export interface State {
|
||||
auth: AuthState
|
||||
}
|
||||
@ -12,25 +11,18 @@ export interface State {
|
||||
const root = (state: State, action: AnyAction) => {
|
||||
switch (action.type) {
|
||||
case HYDRATE:
|
||||
return {...state, ...action.payload};
|
||||
return { ...state, ...action.payload }
|
||||
}
|
||||
|
||||
const combined = combineReducers({
|
||||
auth
|
||||
auth,
|
||||
})
|
||||
|
||||
return combined(state, action);
|
||||
};
|
||||
|
||||
const makeStore: MakeStore<State> = (context) => {
|
||||
return createStore(
|
||||
root,
|
||||
undefined,
|
||||
composeWithDevTools(applyMiddleware(
|
||||
thunkMiddleware,
|
||||
))
|
||||
)
|
||||
return combined(state, action)
|
||||
}
|
||||
|
||||
export const wrapper = createWrapper<State>(makeStore, {debug: true});
|
||||
const makeStore: MakeStore<State> = () => {
|
||||
return createStore(root, undefined, composeWithDevTools(applyMiddleware(thunkMiddleware)))
|
||||
}
|
||||
|
||||
export const wrapper = createWrapper<State>(makeStore, { debug: true })
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user