- ability to change user passwords

- add default page background
- add environment list in [doc](doc/environment.md)
- combined notificationts to become more versatile
- use exported hooks for graphql
- links at the bottom for new users
- fixes for hide contrib setting
- upgrad all packages
This commit is contained in:
Michael Schramm 2021-05-02 12:38:19 +02:00
parent dfbad77d7e
commit 8713c0a8c6
94 changed files with 3384 additions and 5662 deletions

1
.gitignore vendored
View File

@ -26,6 +26,7 @@ yarn-error.log*
.env.development.local
.env.test.local
.env.production.local
.env
# development environments
/.idea

View File

@ -9,12 +9,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added
- ability to change user passwords
- add default page background
- add environment list in [doc](doc/environment.md)
### Changed
- combined notificationts to become more versatile
- use exported hooks for graphql
### Fixed
- links at the bottom for new users
- fixes for hide contrib setting
### Security
- upgrad all packages
## [0.9.9] - 2021-02-14
### Added

View File

@ -1,5 +1,7 @@
.footer {
position: absolute;
padding-left: 16px;
margin-bottom: 4px;
bottom: 0;
left: 0;
right: 0;

View File

@ -1,26 +1,30 @@
import { useQuery } from '@apollo/react-hooks'
import { Button, Select } from 'antd'
import getConfig from 'next/config'
import Link from 'next/link'
import { useRouter } from 'next/router'
import React from 'react'
import GitHubButton from 'react-github-button'
import { useTranslation } from 'react-i18next'
import { SETTINGS_QUERY, SettingsQueryData } from '../../graphql/query/settings.query'
import { useSettingsQuery } from '../../graphql/query/settings.query'
import { languages } from '../../i18n'
import { NextConfigType } from '../../next.config.type'
import { clearAuth, withAuth } from '../with.auth'
import scss from './footer.module.scss'
const { publicRuntimeConfig } = getConfig() as NextConfigType
interface Props {
me?: {
id: string
username: string
roles: string[]
}
}
const AuthFooterInner: React.FC<Props> = (props) => {
const { t, i18n } = useTranslation()
const router = useRouter()
const { data } = useQuery<SettingsQueryData>(SETTINGS_QUERY)
const { data, loading } = useSettingsQuery()
const logout = () => {
clearAuth()
@ -29,22 +33,35 @@ const AuthFooterInner: React.FC<Props> = (props) => {
return (
<footer className={scss.footer}>
<Link href={'/admin'}>
<Button
type={'link'}
ghost
style={{
color: '#FFF',
}}
>
{t('admin')}
</Button>
</Link>
{props.me
? [
<span style={{ color: '#FFF' }} key={'user'}>
Hi, {props.me.username}
</span>,
props.me.roles.includes('admin') && (
<Link key={'admin'} href={'/admin'}>
<Button
type={'link'}
ghost
style={{
color: '#FFF',
}}
>
{t('admin')}
</Button>
</Link>
),
<Link key={'profile'} href={'/admin/profile'}>
<Button
type={'link'}
ghost
style={{
color: '#FFF',
}}
>
{t('profile')}
</Button>
</Link>,
<Button
key={'logout'}
type={'link'}
@ -69,18 +86,19 @@ const AuthFooterInner: React.FC<Props> = (props) => {
{t('login')}
</Button>
</Link>,
<Link href={'/register'} key={'register'}>
<Button
type={'link'}
ghost
disabled={data ? data.disabledSignUp.value : false}
style={{
color: '#FFF',
}}
>
{t('register')}
</Button>
</Link>,
!loading && !data?.disabledSignUp.value && (
<Link href={'/register'} key={'register'}>
<Button
type={'link'}
ghost
style={{
color: '#FFF',
}}
>
{t('register')}
</Button>
</Link>
),
]}
<div style={{ flex: 1 }} />
<Select
@ -99,31 +117,35 @@ const AuthFooterInner: React.FC<Props> = (props) => {
</Select.Option>
))}
</Select>
<GitHubButton type="stargazers" namespace="ohmyform" repo="ohmyform" />
<Button
type={'link'}
target={'_blank'}
rel={'noreferrer'}
ghost
href={'https://www.ohmyform.com'}
style={{
color: '#FFF',
}}
>
OhMyForm
</Button>
<Button
type={'link'}
target={'_blank'}
rel={'noreferrer'}
ghost
href={'https://lokalise.com/'}
style={{
color: '#FFF',
}}
>
translated with Lokalize
</Button>
{!loading && !data?.hideContrib.value && (
<>
<GitHubButton type="stargazers" namespace="ohmyform" repo="ohmyform" />
<Button
type={'link'}
target={'_blank'}
rel={'noreferrer'}
ghost
href={'https://www.ohmyform.com'}
style={{
color: '#FFF',
}}
>
OhMyForm
</Button>
<Button
type={'link'}
target={'_blank'}
rel={'noreferrer'}
ghost
href={'https://lokalise.com/'}
style={{
color: '#FFF',
}}
>
translated with Lokalize
</Button>
</>
)}
</footer>
)
}

View File

@ -1,5 +1,9 @@
import { Layout, Spin } from 'antd'
import getConfig from 'next/config'
import React from 'react'
import { NextConfigType } from '../../next.config.type'
const { publicRuntimeConfig } = getConfig() as NextConfigType
interface Props {
loading?: boolean
@ -11,7 +15,7 @@ export const AuthLayout: React.FC<Props> = (props) => {
<Layout
style={{
height: '100vh',
background: '#437fdc',
background: publicRuntimeConfig.mainBackground,
}}
>
{props.children}

View File

@ -13,17 +13,10 @@ export const DesignTab: React.FC<TabPaneProps> = (props) => {
<Input />
</Form.Item>
{[
'backgroundColor',
'questionColor',
'answerColor',
'buttonColor',
'buttonActiveColor',
'buttonTextColor',
].map((name) => (
{['background', 'question', 'answer', 'button', 'buttonActive', 'buttonText'].map((name) => (
<Form.Item
key={name}
label={t(`form:design.${name}`)}
label={t(`form:design.color.${name}`)}
name={['form', 'design', 'colors', name]}
>
<InputColor />

View File

@ -1,19 +1,9 @@
import { useQuery } from '@apollo/react-hooks'
import { message } from 'antd'
import { useCallback, useState } from 'react'
import ExcelJS from 'exceljs'
import {
ADMIN_FORM_QUERY,
AdminFormQueryData,
AdminFormQueryVariables,
} from '../../../graphql/query/admin.form.query'
import {
ADMIN_PAGER_SUBMISSION_QUERY,
AdminPagerSubmissionEntryQueryData,
AdminPagerSubmissionQueryData,
AdminPagerSubmissionQueryVariables,
} from '../../../graphql/query/admin.pager.submission.query'
import { useImperativeQuery } from '../../use.imerative.query'
import { useCallback, useState } from 'react'
import { SubmissionFragment } from '../../../graphql/fragment/submission.fragment'
import { useFormQuery } from '../../../graphql/query/form.query'
import { useSubmissionPagerImperativeQuery } from '../../../graphql/query/submission.pager.query'
interface Props {
form: string
@ -23,16 +13,13 @@ interface Props {
export const ExportSubmissionAction: React.FC<Props> = (props) => {
const [loading, setLoading] = useState(false)
const form = useQuery<AdminFormQueryData, AdminFormQueryVariables>(ADMIN_FORM_QUERY, {
const form = useFormQuery({
variables: {
id: props.form,
},
})
const getSubmissions = useImperativeQuery<
AdminPagerSubmissionQueryData,
AdminPagerSubmissionQueryVariables
>(ADMIN_PAGER_SUBMISSION_QUERY)
const getSubmissions = useSubmissionPagerImperativeQuery()
const exportSubmissions = useCallback(async () => {
if (loading) {
@ -66,7 +53,7 @@ export const ExportSubmissionAction: React.FC<Props> = (props) => {
start: 0,
})
const buildRow = (data: AdminPagerSubmissionEntryQueryData): any[] => {
const buildRow = (data: SubmissionFragment): any[] => {
const row = [
data.id,
data.created,

View File

@ -4,14 +4,14 @@ import { FormInstance } from 'antd/lib/form'
import { FieldData } from 'rc-field-form/lib/interface'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
import { FormFieldFragment } from '../../../graphql/fragment/form.fragment'
import { adminTypes } from './types'
import { TextType } from './types/text.type'
interface Props {
form: FormInstance
fields: AdminFormFieldFragment[]
onChangeFields: (fields: AdminFormFieldFragment[]) => void
fields: FormFieldFragment[]
onChangeFields: (fields: FormFieldFragment[]) => void
field: FieldData
remove: (index: number) => void
index: number

View File

@ -5,14 +5,14 @@ import { TabPaneProps } from 'antd/lib/tabs'
import { FieldData } from 'rc-field-form/lib/interface'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
import { FormFieldFragment } from '../../../graphql/fragment/form.fragment'
import { FieldCard } from './field.card'
import { adminTypes } from './types'
interface Props extends TabPaneProps {
form: FormInstance
fields: AdminFormFieldFragment[]
onChangeFields: (fields: AdminFormFieldFragment[]) => void
fields: FormFieldFragment[]
onChangeFields: (fields: FormFieldFragment[]) => void
}
export const FieldsTab: React.FC<Props> = (props) => {
@ -55,10 +55,8 @@ export const FieldsTab: React.FC<Props> = (props) => {
<Button
type="dashed"
onClick={() => {
const defaults: AdminFormFieldFragment = {
logicJump: {
enabled: false,
},
const defaults: FormFieldFragment = {
logic: [],
options: [],
id: `NEW-${Date.now()}`,
type: nextType,

View File

@ -0,0 +1,231 @@
import { DeleteOutlined, InfoCircleOutlined } from '@ant-design/icons/lib'
import { Button, Card, Form, Input, Popconfirm, Select, Switch } from 'antd'
import { FormInstance } from 'antd/lib/form'
import { FieldData } from 'rc-field-form/lib/interface'
import React, { useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { FormFieldFragment } from '../../../graphql/fragment/form.fragment'
interface Props {
form: FormInstance
field: FieldData
groups: {
[key: string]: FormFieldFragment[]
}
remove: (index: number) => void
index: number
}
export const NotificationCard: React.FC<Props> = (props) => {
const { t } = useTranslation()
const { form, field, remove, index, groups } = props
const [enabled, setEnabled] = useState<boolean>()
return (
<Card
title={'Notification'}
type={'inner'}
extra={
<div>
<Popconfirm
placement={'left'}
title={t('type:confirmDelete')}
okText={t('type:deleteNow')}
okButtonProps={{ danger: true }}
onConfirm={() => {
remove(index)
}}
>
<Button danger>
<DeleteOutlined />
</Button>
</Popconfirm>
</div>
}
actions={[<DeleteOutlined key={'delete'} onClick={() => remove(index)} />]}
>
<Form.Item
label={t('form:notifications.enabled')}
name={[field.name as string, 'enabled']}
valuePropName={'checked'}
labelCol={{ span: 6 }}
>
<Switch onChange={(e) => setEnabled(e.valueOf())} />
</Form.Item>
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t('form:notifications.subject')}
name={[field.name as string, 'subject']}
rules={[
{
required: Boolean(
form.getFieldValue(['form', 'notifications', field.name as string, 'enabled'])
),
message: t('validation:subjectRequired'),
},
]}
labelCol={{ span: 6 }}
>
<Input />
</Form.Item>
)}
</Form.Item>
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t('form:notifications.htmlTemplate')}
name={[field.name as string, 'htmlTemplate']}
rules={[
{
required: Boolean(
form.getFieldValue(['form', 'notifications', field.name as string, 'enabled'])
),
message: t('validation:templateRequired'),
},
]}
extra={
<div>
<Trans>form:notifications.htmlTemplateInfo</Trans>
<a
href={'https://mjml.io/try-it-live'}
target={'_blank'}
rel={'noreferrer'}
style={{
marginLeft: 16,
}}
>
<InfoCircleOutlined />
</a>
</div>
}
labelCol={{ span: 6 }}
>
<Input.TextArea autoSize />
</Form.Item>
)}
</Form.Item>
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t('form:notifications.fromField')}
name={[field.name as string, 'fromField']}
extra={t('form:notifications.fromFieldInfo')}
labelCol={{ span: 6 }}
rules={[
{
required: Boolean(
form.getFieldValue(['form', 'notifications', field.name as string, 'enabled']) &&
!form.getFieldValue([
'form',
'notifications',
field.name as string,
'fromEmail',
])
),
message: t('validation:emailFieldRequired'),
},
]}
>
<Select>
{Object.keys(groups).map((key) => (
<Select.OptGroup label={key.toUpperCase()} key={key}>
{groups[key].map((element) => (
<Select.Option value={element.id} key={element.id}>
{element.title}
</Select.Option>
))}
</Select.OptGroup>
))}
</Select>
</Form.Item>
)}
</Form.Item>
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t('form:notifications.fromEmail')}
name={[field.name as string, 'fromEmail']}
extra={t('form:notifications.fromEmailInfo')}
labelCol={{ span: 6 }}
rules={[
{
required: Boolean(
form.getFieldValue(['form', 'notifications', field.name as string, 'enabled']) &&
!form.getFieldValue([
'form',
'notifications',
field.name as string,
'fromField',
])
),
message: t('validation:emailFieldRequired'),
},
]}
>
<Input />
</Form.Item>
)}
</Form.Item>
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t('form:notifications.toField')}
name={[field.name as string, 'toField']}
extra={t('form:notifications.toFieldInfo')}
rules={[
{
required: Boolean(
form.getFieldValue(['form', 'notifications', field.name as string, 'enabled']) &&
!form.getFieldValue(['form', 'notifications', field.name as string, 'toEmail'])
),
message: t('validation:emailFieldRequired'),
},
]}
labelCol={{ span: 6 }}
>
<Select>
{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>
))}
</Select.OptGroup>
))}
</Select>
</Form.Item>
)}
</Form.Item>
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t('form:notifications.toEmail')}
name={[field.name as string, 'toEmail']}
extra={t('form:notifications.toEmailInfo')}
labelCol={{ span: 6 }}
rules={[
{
required: Boolean(
form.getFieldValue(['form', 'notifications', field.name as string, 'enabled']) &&
!form.getFieldValue(['form', 'notifications', field.name as string, 'toField'])
),
message: t('validation:emailFieldRequired'),
},
]}
>
<Input />
</Form.Item>
)}
</Form.Item>
</Card>
)
}

View File

@ -0,0 +1,93 @@
import { PlusOutlined } from '@ant-design/icons/lib'
import { Button, Form, Space, Tabs } from 'antd'
import { FormInstance } from 'antd/lib/form'
import { TabPaneProps } from 'antd/lib/tabs'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import {
FormFieldFragment,
FormNotificationFragment,
} from '../../../graphql/fragment/form.fragment'
import { NotificationCard } from './notification.card'
interface Props extends TabPaneProps {
form: FormInstance
fields: FormFieldFragment[]
}
export const NotificationsTab: React.FC<Props> = (props) => {
const { t } = useTranslation()
const groups: {
[key: string]: FormFieldFragment[]
} = {}
props.fields.forEach((field) => {
if (!groups[field.type]) {
groups[field.type] = []
}
groups[field.type].push(field)
})
const addField = useCallback(
(add: (defaults: unknown) => void, index: number) => {
return (
<Form.Item wrapperCol={{ span: 24 }}>
<Space
style={{
width: '100%',
justifyContent: 'flex-end',
}}
>
<Button
type="dashed"
onClick={() => {
const defaults: FormNotificationFragment = {
id: Math.random().toString(),
enabled: false,
}
add(defaults)
}}
>
<PlusOutlined /> {t('form:notifications.add')}
</Button>
</Space>
</Form.Item>
)
},
[props.fields]
)
return (
<Tabs.TabPane {...props}>
<Form.List name={['form', 'notifications']}>
{(fields, { add, remove, move }) => {
const addAndMove = (index) => (defaults) => {
add(defaults)
move(fields.length, index)
}
return (
<div>
{addField(addAndMove(0), 0)}
{fields.map((field, index) => (
<div key={field.key}>
<Form.Item wrapperCol={{ span: 24 }}>
<NotificationCard
form={props.form}
field={field}
index={index}
remove={remove}
groups={groups}
/>
</Form.Item>
{addField(addAndMove(index + 1), index + 1)}
</div>
))}
</div>
)
}}
</Form.List>
</Tabs.TabPane>
)
}

View File

@ -1,131 +0,0 @@
import { InfoCircleOutlined } from '@ant-design/icons/lib'
import { Form, Input, Select, Switch, Tabs } from 'antd'
import { FormInstance } from 'antd/lib/form'
import { TabPaneProps } from 'antd/lib/tabs'
import React, { useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
interface Props extends TabPaneProps {
form: FormInstance
fields: AdminFormFieldFragment[]
}
export const RespondentNotificationsTab: React.FC<Props> = (props) => {
const { t } = useTranslation()
const [enabled, setEnabled] = useState<boolean>()
useEffect(() => {
const next = props.form.getFieldValue(['form', 'respondentNotifications', 'enabled']) as boolean
if (next !== enabled) {
setEnabled(next)
}
}, [props.form.getFieldValue(['form', 'respondentNotifications', 'enabled'])])
useEffect(() => {
props.form
.validateFields([
['form', 'respondentNotifications', 'subject'],
['form', 'respondentNotifications', 'htmlTemplate'],
['form', 'respondentNotifications', 'toField'],
])
.catch((e: Error) => console.error('failed to validate fields', e))
}, [enabled])
const groups: {
[key: string]: AdminFormFieldFragment[]
} = {}
props.fields.forEach((field) => {
if (!groups[field.type]) {
groups[field.type] = []
}
groups[field.type].push(field)
})
return (
<Tabs.TabPane {...props}>
<Form.Item
label={t('form:respondentNotifications.enabled')}
name={['form', 'respondentNotifications', 'enabled']}
valuePropName={'checked'}
>
<Switch onChange={(e) => setEnabled(e.valueOf())} />
</Form.Item>
<Form.Item
label={t('form:respondentNotifications.subject')}
name={['form', 'respondentNotifications', 'subject']}
rules={[
{
required: enabled,
message: t('validation:subjectRequired'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('form:respondentNotifications.htmlTemplate')}
name={['form', 'respondentNotifications', 'htmlTemplate']}
rules={[
{
required: enabled,
message: t('validation:templateRequired'),
},
]}
extra={
<div>
<Trans>form:respondentNotifications.htmlTemplateInfo</Trans>
<a
href={'https://mjml.io/try-it-live'}
target={'_blank'}
rel={'noreferrer'}
style={{
marginLeft: 16,
}}
>
<InfoCircleOutlined />
</a>
</div>
}
>
<Input.TextArea autoSize />
</Form.Item>
<Form.Item
label={t('form:respondentNotifications.toField')}
name={['form', 'respondentNotifications', 'toField']}
extra={t('form:respondentNotifications.toFieldInfo')}
rules={[
{
required: enabled,
message: t('validation:emailFieldRequired'),
},
]}
>
<Select>
{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>
))}
</Select.OptGroup>
))}
</Select>
</Form.Item>
<Form.Item
label={t('form:respondentNotifications.fromEmail')}
name={['form', 'respondentNotifications', 'fromEmail']}
extra={t('form:respondentNotifications.fromEmailInfo')}
>
<Input />
</Form.Item>
</Tabs.TabPane>
)
}

View File

@ -1,123 +0,0 @@
import { InfoCircleOutlined } from '@ant-design/icons/lib'
import { Form, Input, Select, Switch, Tabs } from 'antd'
import { FormInstance } from 'antd/lib/form'
import { TabPaneProps } from 'antd/lib/tabs'
import React, { useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { AdminFormFieldFragment } from '../../../graphql/fragment/admin.form.fragment'
interface Props extends TabPaneProps {
form: FormInstance
fields: AdminFormFieldFragment[]
}
export const SelfNotificationsTab: React.FC<Props> = (props) => {
const { t } = useTranslation()
const [enabled, setEnabled] = useState<boolean>()
useEffect(() => {
const next = props.form.getFieldValue(['form', 'selfNotifications', 'enabled']) as boolean
if (next !== enabled) {
setEnabled(next)
}
}, [props.form.getFieldValue(['form', 'selfNotifications', 'enabled'])])
useEffect(() => {
props.form
.validateFields([
['form', 'selfNotifications', 'subject'],
['form', 'selfNotifications', 'htmlTemplate'],
])
.catch((e: Error) => console.error('failed to validate', e))
}, [enabled])
const groups: {
[key: string]: AdminFormFieldFragment[]
} = {}
props.fields.forEach((field) => {
if (!groups[field.type]) {
groups[field.type] = []
}
groups[field.type].push(field)
})
return (
<Tabs.TabPane {...props}>
<Form.Item
label={t('form:selfNotifications.enabled')}
name={['form', 'selfNotifications', 'enabled']}
valuePropName={'checked'}
>
<Switch onChange={(e) => setEnabled(e.valueOf())} />
</Form.Item>
<Form.Item
label={t('form:selfNotifications.subject')}
name={['form', 'selfNotifications', 'subject']}
rules={[
{
required: enabled,
message: t('validation:subjectRequired'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('form:selfNotifications.htmlTemplate')}
name={['form', 'selfNotifications', 'htmlTemplate']}
rules={[
{
required: enabled,
message: t('validation:templateRequired'),
},
]}
extra={
<div>
<Trans>form:selfNotifications.htmlTemplateInfo</Trans>
<a
href={'https://mjml.io/try-it-live'}
target={'_blank'}
rel={'noreferrer'}
style={{
marginLeft: 16,
}}
>
<InfoCircleOutlined />
</a>
</div>
}
>
<Input.TextArea autoSize />
</Form.Item>
<Form.Item
label={t('form:selfNotifications.fromField')}
name={['form', 'selfNotifications', 'fromField']}
extra={t('form:selfNotifications.fromFieldInfo')}
>
<Select>
{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>
))}
</Select.OptGroup>
))}
</Select>
</Form.Item>
<Form.Item
label={t('form:selfNotifications.toEmail')}
name={['form', 'selfNotifications', 'toEmail']}
extra={t('form:selfNotifications.toEmailInfo')}
>
<Input />
</Form.Item>
</Tabs.TabPane>
)
}

View File

@ -2,24 +2,24 @@ import { Descriptions, Table } from 'antd'
import { ColumnsType } from 'antd/lib/table/interface'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { FormPagerFragment } from '../../../graphql/fragment/form.pager.fragment'
import {
AdminPagerSubmissionEntryFieldQueryData,
AdminPagerSubmissionEntryQueryData,
AdminPagerSubmissionFormQueryData,
} from '../../../graphql/query/admin.pager.submission.query'
SubmissionFragment,
SubmissionFragmentField,
} from '../../../graphql/fragment/submission.fragment'
interface Props {
form: AdminPagerSubmissionFormQueryData
submission: AdminPagerSubmissionEntryQueryData
form: FormPagerFragment
submission: SubmissionFragment
}
export const SubmissionValues: React.FC<Props> = (props) => {
const { t } = useTranslation()
const columns: ColumnsType<AdminPagerSubmissionEntryFieldQueryData> = [
const columns: ColumnsType<SubmissionFragmentField> = [
{
title: t('submission:field'),
render(row: AdminPagerSubmissionEntryFieldQueryData) {
render(_, row) {
if (row.field) {
return `${row.field.title}${row.field.required ? '*' : ''}`
}
@ -29,10 +29,12 @@ export const SubmissionValues: React.FC<Props> = (props) => {
},
{
title: t('submission:value'),
render(row: AdminPagerSubmissionEntryFieldQueryData) {
render(_, row) {
try {
const data = JSON.parse(row.value) as { value: string }
console.log('DATA', data)
return data.value
} catch (e) {
return row.value

View File

@ -6,7 +6,6 @@ import { AdminFieldTypeProps } from './type.props'
export const RatingType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation()
// TODO add ratings
return (
<div>
<Form.Item

View File

@ -6,7 +6,6 @@ import { AdminFieldTypeProps } from './type.props'
export const YesNoType: React.FC<AdminFieldTypeProps> = (props) => {
const { t } = useTranslation()
// TODO add switch
return (
<div>
<Form.Item

View File

@ -1,7 +1,10 @@
import { Form, message } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import React from 'react'
import { FormDesignFragment, FormFieldFragment } from '../../graphql/fragment/form.fragment'
import {
FormPublicDesignFragment,
FormPublicFieldFragment,
} from '../../graphql/fragment/form.public.fragment'
import { StyledButton } from '../styled/button'
import { StyledH1 } from '../styled/h1'
import { StyledMarkdown } from '../styled/markdown'
@ -11,8 +14,8 @@ import { TextType } from './types/text.type'
import { FieldTypeProps } from './types/type.props'
interface Props {
field: FormFieldFragment
design: FormDesignFragment
field: FormPublicFieldFragment
design: FormPublicDesignFragment
// eslint-disable-next-line @typescript-eslint/no-explicit-any
save: (data: any) => void
@ -84,9 +87,9 @@ export const Field: React.FC<Props> = ({ field, save, design, next, prev, ...pro
}}
>
<StyledButton
background={design.colors.buttonColor}
color={design.colors.buttonTextColor}
highlight={design.colors.buttonActiveColor}
background={design.colors.button}
color={design.colors.buttonText}
highlight={design.colors.buttonActive}
onClick={prev}
>
{'Previous'}
@ -95,9 +98,9 @@ export const Field: React.FC<Props> = ({ field, save, design, next, prev, ...pro
<div style={{ flex: 1 }} />
<StyledButton
background={design.colors.buttonColor}
color={design.colors.buttonTextColor}
highlight={design.colors.buttonActiveColor}
background={design.colors.button}
color={design.colors.buttonText}
highlight={design.colors.buttonActive}
size={'large'}
onClick={form.submit}
>

View File

@ -1,6 +1,9 @@
import { Space } from 'antd'
import React from 'react'
import { FormDesignFragment, FormPageFragment } from '../../graphql/fragment/form.fragment'
import {
FormPublicDesignFragment,
FormPublicPageFragment,
} from '../../graphql/fragment/form.public.fragment'
import { StyledButton } from '../styled/button'
import { StyledH1 } from '../styled/h1'
import { StyledMarkdown } from '../styled/markdown'
@ -8,8 +11,8 @@ import scss from './page.module.scss'
interface Props {
type: 'start' | 'end'
page: FormPageFragment
design: FormDesignFragment
page: FormPublicPageFragment
design: FormPublicDesignFragment
className?: string
next: () => void
@ -59,9 +62,9 @@ export const FormPage: React.FC<Props> = ({ page, design, next, prev, className,
<div style={{ flex: 1 }} />
<StyledButton
background={design.colors.buttonColor}
color={design.colors.buttonTextColor}
highlight={design.colors.buttonActiveColor}
background={design.colors.button}
color={design.colors.buttonText}
highlight={design.colors.buttonActive}
size={'large'}
onClick={next}
>

View File

@ -5,7 +5,6 @@ import { FieldTypeProps } from './type.props'
export const RatingType: React.FC<FieldTypeProps> = ({ field, urlValue }) => {
const { t } = useTranslation()
// TODO add ratings
let initialValue: number = undefined

View File

@ -1,7 +1,10 @@
import { FormDesignFragment, FormFieldFragment } from '../../../graphql/fragment/form.fragment'
import {
FormPublicDesignFragment,
FormPublicFieldFragment,
} from '../../../graphql/fragment/form.public.fragment'
export interface FieldTypeProps {
field: FormFieldFragment
design: FormDesignFragment
field: FormPublicFieldFragment
design: FormPublicDesignFragment
urlValue?: string
}

View File

@ -1,10 +1,9 @@
import { useQuery } from '@apollo/react-hooks'
import React from 'react'
import { SETTINGS_QUERY, SettingsQueryData } from '../graphql/query/settings.query'
import { useSettingsQuery } from '../graphql/query/settings.query'
import scss from './omf.module.scss'
export const Omf: React.FC = () => {
const { data, loading } = useQuery<SettingsQueryData>(SETTINGS_QUERY)
const { data, loading } = useSettingsQuery()
if (loading || (data && data.hideContrib.value)) {
return null

View File

@ -1,15 +1,14 @@
import { CaretDownOutlined, UserOutlined } from '@ant-design/icons'
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons/lib'
import { useQuery } from '@apollo/react-hooks'
import { Dropdown, Layout, Menu, PageHeader, Select, Space, Spin, Tag } from 'antd'
import { Alert, Dropdown, Layout, Menu, PageHeader, Select, Space, Spin, Tag } from 'antd'
import Link from 'next/link'
import { useRouter } from 'next/router'
import React, { CSSProperties, FunctionComponent } from 'react'
import GitHubButton from 'react-github-button'
import { useTranslation } from 'react-i18next'
import { ME_QUERY, MeQueryData } from '../graphql/query/me.query'
import { useMeQuery } from '../graphql/query/me.query'
import { languages } from '../i18n'
import { sideMenu, SideMenuElement } from './sidemenu'
import GitHubButton from 'react-github-button'
import { useWindowSize } from './use.window.size'
import { clearAuth } from './with.auth'
@ -33,9 +32,10 @@ interface Props {
title?: string
subTitle?: string
extra?: JSX.Element[]
error?: string
}
const Structure: FunctionComponent<Props> = (props) => {
export const Structure: FunctionComponent<Props> = (props) => {
const { t, i18n } = useTranslation()
const size = useWindowSize()
const [userMenu, setUserMenu] = React.useState(false)
@ -43,7 +43,7 @@ const Structure: FunctionComponent<Props> = (props) => {
const [selected, setSelected] = React.useState<string[]>()
const [sidebar, setSidebar] = React.useState(size.width < 700)
const router = useRouter()
const user = useQuery<MeQueryData>(ME_QUERY)
const user = useMeQuery()
React.useEffect(() => {
if (sidebar !== size.width < 700) {
@ -76,7 +76,7 @@ const Structure: FunctionComponent<Props> = (props) => {
return false
}
return user.data.me.roles.includes(element.role)
return user.data?.me.roles.includes(element.role)
})
.map(
(element): JSX.Element => {
@ -249,7 +249,9 @@ const Structure: FunctionComponent<Props> = (props) => {
</Menu.Item>
</Menu>
</Sider>
<Layout style={{ padding: '0 24px 24px' }}>
<Layout
style={{ padding: '0 24px 24px', minHeight: 500, height: '100%', overflow: 'auto' }}
>
{props.title && (
<PageHeader
title={props.title}
@ -285,6 +287,10 @@ const Structure: FunctionComponent<Props> = (props) => {
/>
)}
{props.error && (
<Alert message={props.error} type={'error'} style={{ marginBottom: 24 }} />
)}
<Spin spinning={!!props.loading}>
<Content
style={{

View File

@ -3,15 +3,15 @@ import { PickerProps } from 'antd/lib/date-picker/generatePicker'
import { Moment } from 'moment'
import React from 'react'
import styled from 'styled-components'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import { FormPublicDesignFragment } from '../../graphql/fragment/form.public.fragment'
import { transparentize } from './color.change'
type Props = { design: FormDesignFragment } & PickerProps<Moment>
type Props = { design: FormPublicDesignFragment } & PickerProps<Moment>
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(DatePicker)`
color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
border-color: ${(props: Props) => props.design.colors.answer};
background: none !important;
border-right: none;
border-top: none;
@ -21,7 +21,7 @@ const Field = styled(DatePicker)`
:hover,
:active {
border-color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answer};
}
&.ant-picker {
@ -33,15 +33,15 @@ const Field = styled(DatePicker)`
}
input {
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
::placeholder {
color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
color: ${(props: Props) => transparentize(props.design.colors.answer, 60)};
}
}
.anticon {
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
}
`

View File

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

View File

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

View File

@ -2,17 +2,17 @@ import { Input } from 'antd'
import { InputProps } from 'antd/lib/input/Input'
import React from 'react'
import styled from 'styled-components'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import { FormPublicDesignFragment } from '../../graphql/fragment/form.public.fragment'
import { transparentize } from './color.change'
interface Props extends InputProps {
design: FormDesignFragment
design: FormPublicDesignFragment
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(Input)`
color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
border-color: ${(props: Props) => props.design.colors.answer};
background: none !important;
border-right: none;
border-top: none;
@ -20,12 +20,12 @@ const Field = styled(Input)`
border-radius: 0;
:focus {
outline: ${(props: Props) => props.design.colors.answerColor} auto 5px;
outline: ${(props: Props) => props.design.colors.answer} auto 5px;
}
:hover,
:active {
border-color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answer};
}
&.ant-input-affix-wrapper {
@ -34,15 +34,15 @@ const Field = styled(Input)`
input {
background: none !important;
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
::placeholder {
color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
color: ${(props: Props) => transparentize(props.design.colors.answer, 60)};
}
}
.anticon {
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
}
`

View File

@ -1,16 +1,16 @@
import React from 'react'
import ReactMarkdown, { ReactMarkdownProps } from 'react-markdown'
import styled from 'styled-components'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import { FormPublicDesignFragment } from '../../graphql/fragment/form.public.fragment'
import { lighten } from './color.change'
interface Props extends ReactMarkdownProps {
type: 'question' | 'answer'
design: FormDesignFragment
design: FormPublicDesignFragment
}
const getColor = (props: Props) =>
props.type === 'question' ? props.design.colors.questionColor : props.design.colors.answerColor
props.type === 'question' ? props.design.colors.question : props.design.colors.answer
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
const Markdown = styled(ReactMarkdown)`

View File

@ -2,17 +2,17 @@ import { InputNumber } from 'antd'
import { InputNumberProps } from 'antd/lib/input-number'
import React from 'react'
import styled from 'styled-components'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import { FormPublicDesignFragment } from '../../graphql/fragment/form.public.fragment'
import { transparentize } from './color.change'
interface Props extends InputNumberProps {
design: FormDesignFragment
design: FormPublicDesignFragment
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(InputNumber)`
color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
border-color: ${(props: Props) => props.design.colors.answer};
background: none !important;
border-right: none;
border-top: none;
@ -21,12 +21,12 @@ const Field = styled(InputNumber)`
width: 100%;
:focus {
outline: ${(props: Props) => props.design.colors.answerColor} auto 5px;
outline: ${(props: Props) => props.design.colors.answer} auto 5px;
}
:hover,
:active {
border-color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answer};
}
&.ant-input-number {
@ -35,15 +35,15 @@ const Field = styled(InputNumber)`
input {
background: none !important;
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
::placeholder {
color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
color: ${(props: Props) => transparentize(props.design.colors.answer, 60)};
}
}
.anticon {
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
}
`

View File

@ -1,18 +1,16 @@
import React from 'react'
import styled from 'styled-components'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import { FormPublicDesignFragment } from '../../graphql/fragment/form.public.fragment'
interface Props {
type: 'question' | 'answer'
design: FormDesignFragment
design: FormPublicDesignFragment
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
const Paragraph = styled.p`
color: ${(props: Props) =>
props.type === 'question'
? props.design.colors.questionColor
: props.design.colors.answerColor};
props.type === 'question' ? props.design.colors.question : props.design.colors.answer};
`
export const StyledP: React.FC<Props> = ({ children, ...props }) => {

View File

@ -2,34 +2,34 @@ import { Radio } from 'antd'
import { RadioProps } from 'antd/lib/radio/interface'
import React from 'react'
import styled from 'styled-components'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import { FormPublicDesignFragment } from '../../graphql/fragment/form.public.fragment'
interface Props extends RadioProps {
design: FormDesignFragment
design: FormPublicDesignFragment
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(Radio)`
color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
border-color: ${(props: Props) => props.design.colors.answer};
background: none;
.ant-radio {
.ant-radio-inner {
border-color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answer};
&::after {
background: ${(props: Props) => props.design.colors.answerColor};
background: ${(props: Props) => props.design.colors.answer};
}
}
&::after {
border-color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answer};
}
}
.anticon {
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
}
`

View File

@ -2,18 +2,18 @@ import { Select } from 'antd'
import { SelectProps } from 'antd/lib/select'
import React from 'react'
import styled from 'styled-components'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import { FormPublicDesignFragment } from '../../graphql/fragment/form.public.fragment'
import { transparentize } from './color.change'
interface Props extends SelectProps<string> {
design: FormDesignFragment
design: FormPublicDesignFragment
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(Select)`
.ant-select-selector {
color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answerColor} !important;
color: ${(props: Props) => props.design.colors.answer};
border-color: ${(props: Props) => props.design.colors.answer} !important;
background: none !important;
border-right: none !important;
border-top: none !important;
@ -23,25 +23,25 @@ const Field = styled(Select)`
}
:focus {
outline: ${(props: Props) => props.design.colors.answerColor} auto 5px;
outline: ${(props: Props) => props.design.colors.answer} auto 5px;
}
:hover,
:active {
border-color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answer};
}
input {
background: none !important;
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
::placeholder {
color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
color: ${(props: Props) => transparentize(props.design.colors.answer, 60)};
}
}
.anticon {
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
}
`

View File

@ -2,17 +2,17 @@ import { Input } from 'antd'
import { TextAreaProps } from 'antd/lib/input/TextArea'
import React from 'react'
import styled from 'styled-components'
import { FormDesignFragment } from '../../graphql/fragment/form.fragment'
import { FormPublicDesignFragment } from '../../graphql/fragment/form.public.fragment'
import { transparentize } from './color.change'
interface Props extends TextAreaProps {
design: FormDesignFragment
design: FormPublicDesignFragment
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const Field = styled(Input.TextArea)`
color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
border-color: ${(props: Props) => props.design.colors.answer};
background: none !important;
border-right: none;
border-top: none;
@ -22,25 +22,25 @@ const Field = styled(Input.TextArea)`
:focus {
outline: none;
box-shadow: none;
border-color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answer};
}
:hover,
:active {
border-color: ${(props: Props) => props.design.colors.answerColor};
border-color: ${(props: Props) => props.design.colors.answer};
}
input {
background: none !important;
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
::placeholder {
color: ${(props: Props) => transparentize(props.design.colors.answerColor, 60)};
color: ${(props: Props) => transparentize(props.design.colors.answer, 60)};
}
}
.anticon {
color: ${(props: Props) => props.design.colors.answerColor};
color: ${(props: Props) => props.design.colors.answer};
}
`

View File

@ -1,4 +1,4 @@
import { useMutation } from '@apollo/react-hooks'
import { useMutation } from '@apollo/client'
import { useCallback, useEffect, useState } from 'react'
import {
SUBMISSION_SET_FIELD_MUTATION,

View File

@ -1,16 +1,12 @@
import { useQuery } from '@apollo/react-hooks'
import { AxiosRequestConfig } from 'axios'
import { useRouter } from 'next/router'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ME_QUERY, MeQueryData } from '../graphql/query/me.query'
import { useMeQuery } from '../graphql/query/me.query'
import { LoadingPage } from './loading.page'
export const clearAuth = (): void => {
localStorage.removeItem('access')
localStorage.removeItem('refresh')
// TODO logout on server!
}
export const setAuth = (access: string, refresh: string): void => {
@ -18,26 +14,6 @@ export const setAuth = (access: string, refresh: string): void => {
localStorage.setItem('refresh', refresh)
}
export const authConfig = (config: AxiosRequestConfig = {}): AxiosRequestConfig => {
if (!config.headers) {
config.headers = {}
}
try {
const token = localStorage.getItem('access')
// TODO check for validity / use refresh token
if (token) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
config.headers.Authorization = `Bearer ${token}`
}
} catch (e) {
return config
}
return config
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
export const withAuth = (Component: any, roles: string[] = [], optional?: boolean): React.FC => {
// eslint-disable-next-line react/display-name
@ -45,7 +21,7 @@ export const withAuth = (Component: any, roles: string[] = [], optional?: boolea
const { t } = useTranslation()
const router = useRouter()
const [access, setAccess] = useState(false)
const { loading, data, error } = useQuery<MeQueryData>(ME_QUERY)
const { loading, data, error } = useMeQuery()
useEffect(() => {
if (roles.length === 0) {
@ -81,11 +57,11 @@ export const withAuth = (Component: any, roles: string[] = [], optional?: boolea
if (!optional) {
if (loading) {
return <LoadingPage message={t('loadingCredentials')}/>
return <LoadingPage message={t('loadingCredentials')} />
}
if (!access) {
return <LoadingPage message={t('checkingCredentials')}/>
return <LoadingPage message={t('checkingCredentials')} />
}
}

8
doc/environment.md Normal file
View File

@ -0,0 +1,8 @@
# Environment Variables
| Name | Default Value | Description |
| ---- | ------------- | ----------- |
| ENDPOINT | `/graphql` | Path to graphql api endpoint |
| SERVER_ENDPOINT | *ENDPOINT* | if endpoint is relative it should point to a resolvable url of graphql api endpoint |
| MAIN_BACKGROUND | *not set* | background color of home page |
| SPA | `false` | dependent on the build mode this must be set to true to have working routing

45
graphql/client.ts Normal file
View File

@ -0,0 +1,45 @@
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import 'isomorphic-fetch'
import getConfig from 'next/config'
import { NextConfigType } from '../next.config.type'
let client: ApolloClient<any>
const getClient = (): ApolloClient<any> => {
if (!client) {
const config = getConfig() as NextConfigType
if (!config) return client
const { publicRuntimeConfig, serverRuntimeConfig } = config
client = new ApolloClient({
cache: new InMemoryCache(),
link: from([
setContext((request, context) => {
const headers: { [key: string]: string } = {}
if (process.browser) {
const access = localStorage.getItem('access')
if (access) {
headers.authorization = `Bearer ${access}`
}
}
return {
headers,
}
}),
new HttpLink({
uri: serverRuntimeConfig.endpoint || publicRuntimeConfig.endpoint,
}),
]),
})
}
return client
}
export default getClient

View File

@ -1,209 +0,0 @@
import { gql } from 'apollo-boost'
export interface AdminFormPageFragment {
show: boolean
title?: string
paragraph?: string
buttonText?: string
buttons: {
url?: string
action?: string
text?: string
bgColor?: string
color?: string
}[]
}
export interface AdminFormFieldOptionFragment {
key?: string
title?: string
value: string
}
export interface AdminFormFieldOptionKeysFragment {
[key: string]: string
}
export interface AdminFormFieldLogicJumpFragment {
fieldA?: string
valueB?: string
expressionString?: string
jumpTo?: string
enabled: boolean
}
export interface AdminFormFieldFragment {
id: string
title: string
slug?: string
type: string
description: string
required: boolean
value: string
options: AdminFormFieldOptionFragment[]
optionKeys?: AdminFormFieldOptionKeysFragment
logicJump: AdminFormFieldLogicJumpFragment
rating?: {
steps?: number
shape?: string
}
}
export interface AdminFormHookFragment {
id: string
enabled: boolean
url?: string
format?: string
}
export interface AdminFormFragment {
id?: string
title: string
created: string
lastModified?: string
language: string
showFooter: boolean
isLive: boolean
fields: AdminFormFieldFragment[]
hooks: AdminFormHookFragment[]
selfNotifications: {
enabled: boolean
subject?: string
htmlTemplate?: string
fromField?: string
toEmail?: string
}
respondentNotifications: {
enabled: boolean
subject?: string
htmlTemplate?: string
toField?: string
fromEmail?: string
}
design: {
colors: {
backgroundColor: string
questionColor: string
answerColor: string
buttonColor: string
buttonTextColor: string
}
font?: string
}
startPage: AdminFormPageFragment
endPage: AdminFormPageFragment
admin: {
id: string
username: string
email: string
}
}
export const ADMIN_FORM_FRAGMENT = gql`
fragment AdminForm on Form {
id
title
created
lastModified
language
showFooter
isLive
hooks {
id
enabled
format
url
}
fields {
id
title
slug
type
description
required
value
options {
key
title
value
}
logicJump {
fieldA
valueB
expressionString
jumpTo
enabled
}
rating {
steps
shape
}
}
selfNotifications {
enabled
subject
htmlTemplate
fromField
toEmail
}
respondentNotifications {
enabled
subject
htmlTemplate
toField
fromEmail
}
design {
colors {
backgroundColor
questionColor
answerColor
buttonColor
buttonActiveColor
buttonTextColor
}
font
}
startPage {
show
title
paragraph
buttonText
buttons {
url
action
text
bgColor
activeColor
color
}
}
endPage {
show
title
paragraph
buttonText
buttons {
url
action
text
bgColor
activeColor
color
}
}
admin {
id
username
email
}
}
`

View File

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

View File

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

View File

@ -1,32 +1,41 @@
import { gql } from 'apollo-boost'
import { gql } from '@apollo/client/core'
export interface FormPageFragment {
id: string
show: boolean
title?: string
paragraph?: string
buttonText?: string
buttons: {
id: string
url?: string
action?: string
text?: string
bgColor?: string
activeColor?: string
color?: string
}[]
}
export interface FormFieldOptionFragment {
id: string
key?: string
title?: string
value: string
}
export interface FormFieldLogicJumpFragment {
fieldA?: string
valueB?: string
expressionString?: string
jumpTo?: string
export interface FormFieldOptionKeysFragment {
[key: string]: string
}
export interface FormFieldLogicFragment {
id: string
action: string
formula: string
enabled: boolean
jumpTo?: string
require?: boolean
visible?: boolean
disable?: boolean
}
export interface FormFieldFragment {
@ -39,8 +48,9 @@ export interface FormFieldFragment {
value: string
options: FormFieldOptionFragment[]
optionKeys?: FormFieldOptionKeysFragment
logicJump: FormFieldLogicJumpFragment
logic: FormFieldLogicFragment[]
rating?: {
steps?: number
@ -48,36 +58,70 @@ export interface FormFieldFragment {
}
}
export interface FormDesignFragment {
colors: {
backgroundColor: string
questionColor: string
answerColor: string
buttonColor: string
buttonActiveColor: string
buttonTextColor: string
}
font?: string
export interface FormHookFragment {
id: string
enabled: boolean
url?: string
format?: string
}
export interface FormNotificationFragment {
id: string
enabled: boolean
subject?: string
htmlTemplate?: string
toField?: string
toEmail?: string
fromField?: string
fromEmail?: string
}
export interface FormFragment {
id?: string
title: string
created: string
lastModified?: string
language: string
showFooter: boolean
isLive: boolean
fields: FormFieldFragment[]
design: FormDesignFragment
hooks: FormHookFragment[]
notifications: FormNotificationFragment[]
design: {
colors: {
background: string
question: string
answer: string
button: string
buttonText: string
}
font?: string
}
startPage: FormPageFragment
endPage: FormPageFragment
admin: {
id: string
username: string
email: string
}
}
export const FORM_FRAGMENT = gql`
fragment Form on Form {
id
title
created
lastModified
language
showFooter
isLive
hooks {
id
enabled
format
url
}
fields {
id
@ -89,17 +133,21 @@ export const FORM_FRAGMENT = gql`
value
options {
id
key
title
value
}
logicJump {
fieldA
valueB
expressionString
jumpTo
logic {
id
action
formula
enabled
jumpTo
require
visible
disable
}
rating {
steps
@ -107,24 +155,36 @@ export const FORM_FRAGMENT = gql`
}
}
notifications {
id
enabled
subject
htmlTemplate
fromField
fromEmail
toField
toEmail
}
design {
colors {
backgroundColor
questionColor
answerColor
buttonColor
buttonActiveColor
buttonTextColor
background
question
answer
button
buttonActive
buttonText
}
font
}
startPage {
id
show
title
paragraph
buttonText
buttons {
id
url
action
text
@ -133,13 +193,14 @@ export const FORM_FRAGMENT = gql`
color
}
}
endPage {
id
show
title
paragraph
buttonText
buttons {
id
url
action
text
@ -148,5 +209,10 @@ export const FORM_FRAGMENT = gql`
color
}
}
admin {
id
username
email
}
}
`

View File

@ -0,0 +1,31 @@
import { gql } from '@apollo/client/core'
export interface FormPagerFragment {
id: string
created: string
lastModified?: string
title: string
isLive: boolean
language: string
admin: {
id: string
email: string
username: string
}
}
export const FORM_PAGER_FRAGMENT = gql`
fragment Form on Form {
id
created
lastModified
title
isLive
language
admin {
id
email
username
}
}
`

View File

@ -0,0 +1,173 @@
import { gql } from '@apollo/client/core'
export interface FormPublicPageFragment {
id: string
show: boolean
title?: string
paragraph?: string
buttonText?: string
buttons: {
id: string
url?: string
action?: string
text?: string
bgColor?: string
activeColor?: string
color?: string
}[]
}
export interface FormPublicFieldOptionFragment {
key?: string
title?: string
value: string
}
export interface FormPublicFieldLogicFragment {
id: string
action: string
formula: string
jumpTo?: string
require?: boolean
visible?: boolean
disable?: boolean
}
export interface FormPublicFieldFragment {
id: string
title: string
slug?: string
type: string
description: string
required: boolean
value: string
options: FormPublicFieldOptionFragment[]
logic: FormPublicFieldLogicFragment[]
rating?: {
steps?: number
shape?: string
}
}
export interface FormPublicDesignFragment {
colors: {
background: string
question: string
answer: string
button: string
buttonActive: string
buttonText: string
}
font?: string
}
export interface FormPublicFragment {
id?: string
title: string
created: string
language: string
showFooter: boolean
fields: FormPublicFieldFragment[]
design: FormPublicDesignFragment
startPage: FormPublicPageFragment
endPage: FormPublicPageFragment
}
export const FORM_PUBLIC_FRAGMENT = gql`
fragment Form on Form {
id
title
language
showFooter
fields {
id
title
slug
type
description
required
value
logic {
id
formula
action
disable
jumpTo
require
visible
}
options {
id
key
title
value
}
logic {
id
action
formula
jumpTo
require
visible
disable
}
rating {
steps
shape
}
}
design {
colors {
background
question
answer
button
buttonActive
buttonText
}
font
}
startPage {
id
show
title
paragraph
buttonText
buttons {
id
url
action
text
bgColor
activeColor
color
}
}
endPage {
id
show
title
paragraph
buttonText
buttons {
id
url
action
text
bgColor
activeColor
color
}
}
}
`

View File

@ -1,16 +1,61 @@
import { gql } from 'apollo-boost'
import { gql } from '@apollo/client/core'
export interface SubmissionFragment {
id?: string
interface SubmissionFragmentFormField {
title: string
created: string
required: boolean
}
export const FORM_FRAGMENT = gql`
export interface SubmissionFragmentField {
id: string
value: string
type: string
field?: SubmissionFragmentFormField
}
export interface SubmissionFragment {
id: string
created: string
lastModified?: string
percentageComplete: number
timeElapsed: number
geoLocation: {
country: string
city: string
}
device: {
type: string
name: string
}
fields: SubmissionFragmentField[]
}
export const SUBMISSION_FRAGMENT = gql`
fragment Submission on Submission {
id
title
language
showFooter
created
lastModified
percentageComplete
timeElapsed
geoLocation {
country
city
}
device {
type
name
}
fields {
id
value
type
field {
title
required
}
}
}
`

View File

@ -0,0 +1,19 @@
import { gql } from '@apollo/client/core'
export interface UserPagerFragment {
id: string
roles: string[]
verifiedEmail: boolean
email: string
created: string
}
export const USER_PAGER_FRAGMENT = gql`
fragment User on User {
id
roles
verifiedEmail
email
created
}
`

View File

@ -1,20 +0,0 @@
import { gql } from 'apollo-boost'
import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
export interface AdminFormCreateMutationData {
form: AdminFormFragment
}
export interface AdminFormCreateMutationVariables {
form: AdminFormFragment
}
export const ADMIN_FORM_CREATE_MUTATION = gql`
mutation update($form: FormCreateInput!) {
form: createForm(form: $form) {
...AdminForm
}
}
${ADMIN_FORM_FRAGMENT}
`

View File

@ -1,19 +0,0 @@
import { gql } from 'apollo-boost'
export interface AdminFormDeleteMutationData {
form: {
id
}
}
export interface AdminFormDeleteMutationVariables {
id: string
}
export const ADMIN_FORM_DELETE_MUTATION = gql`
mutation delete($id: ID!) {
form: deleteForm(id: $id) {
id
}
}
`

View File

@ -1,20 +0,0 @@
import { gql } from 'apollo-boost'
import { ADMIN_FORM_FRAGMENT, AdminFormFragment } from '../fragment/admin.form.fragment'
export interface AdminFormUpdateMutationData {
form: AdminFormFragment
}
export interface AdminFormUpdateMutationVariables {
form: AdminFormFragment
}
export const ADMIN_FORM_UPDATE_MUTATION = gql`
mutation update($form: FormUpdateInput!) {
form: updateForm(form: $form) {
...AdminForm
}
}
${ADMIN_FORM_FRAGMENT}
`

View File

@ -1,21 +0,0 @@
import { gql } from 'apollo-boost'
import { ADMIN_PROFILE_FRAGMENT } from '../fragment/admin.profile.fragment'
import { AdminUserFragment } from '../fragment/admin.user.fragment'
export interface AdminProfileUpdateMutationData {
user: AdminUserFragment
}
export interface AdminProfileUpdateMutationVariables {
user: AdminUserFragment
}
export const ADMIN_PROFILE_UPDATE_MUTATION = gql`
mutation update($user: ProfileUpdateInput!) {
form: updateProfile(user: $user) {
...AdminProfile
}
}
${ADMIN_PROFILE_FRAGMENT}
`

View File

@ -1,19 +0,0 @@
import { gql } from 'apollo-boost'
export interface AdminUserDeleteMutationData {
form: {
id
}
}
export interface AdminUserDeleteMutationVariables {
id: string
}
export const ADMIN_USER_DELETE_MUTATION = gql`
mutation delete($id: ID!) {
form: deleteUser(id: $id) {
id
}
}
`

View File

@ -1,20 +0,0 @@
import { gql } from 'apollo-boost'
import { ADMIN_USER_FRAGMENT, AdminUserFragment } from '../fragment/admin.user.fragment'
export interface AdminUserUpdateMutationData {
user: AdminUserFragment
}
export interface AdminUserUpdateMutationVariables {
user: AdminUserFragment
}
export const ADMIN_USER_UPDATE_MUTATION = gql`
mutation update($user: UserUpdateInput!) {
form: updateUser(user: $user) {
...AdminUser
}
}
${ADMIN_USER_FRAGMENT}
`

View File

@ -0,0 +1,30 @@
import { MutationHookOptions, MutationTuple, useMutation } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { FORM_FRAGMENT, FormFragment } from '../fragment/form.fragment'
interface Data {
form: FormFragment
}
interface Variables {
form: {
isLive: boolean
language: string
showFooter?: boolean
title: string
}
}
const MUTATION = gql`
mutation update($form: FormCreateInput!) {
form: createForm(form: $form) {
...Form
}
}
${FORM_FRAGMENT}
`
export const useFormCreateMutation = (
data?: MutationHookOptions<Data, Variables>
): MutationTuple<Data, Variables> => useMutation<Data, Variables>(MUTATION, data)

View File

@ -0,0 +1,24 @@
import { MutationHookOptions, MutationTuple, useMutation } from '@apollo/client'
import { gql } from '@apollo/client/core'
interface Data {
form: {
id
}
}
interface Variables {
id: string
}
const MUTATION = gql`
mutation delete($id: ID!) {
form: deleteForm(id: $id) {
id
}
}
`
export const useFormDeleteMutation = (
data?: MutationHookOptions<Data, Variables>
): MutationTuple<Data, Variables> => useMutation<Data, Variables>(MUTATION, data)

View File

@ -0,0 +1,25 @@
import { MutationHookOptions, MutationTuple, useMutation } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { FORM_FRAGMENT, FormFragment } from '../fragment/form.fragment'
interface Data {
form: FormFragment
}
interface Variables {
form: FormFragment
}
const MUTATION = gql`
mutation update($form: FormUpdateInput!) {
form: updateForm(form: $form) {
...Form
}
}
${FORM_FRAGMENT}
`
export const useFormUpdateMutation = (
data?: MutationHookOptions<Data, Variables>
): MutationTuple<Data, Variables> => useMutation<Data, Variables>(MUTATION, data)

View File

@ -1,4 +1,4 @@
import { gql } from 'apollo-boost'
import { gql } from '@apollo/client/core'
export interface LoginMutationData {
tokens: {

View File

@ -0,0 +1,34 @@
import { MutationHookOptions, MutationTuple, useMutation } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { ADMIN_PROFILE_FRAGMENT } from '../fragment/admin.profile.fragment'
import { AdminUserFragment } from '../fragment/admin.user.fragment'
export interface Data {
user: AdminUserFragment
}
export interface Variables {
user: {
email?: string
firstName?: string
id: string
language?: string
lastName?: string
password?: string
username?: string
}
}
export const MUTATION = gql`
mutation update($user: ProfileUpdateInput!) {
form: updateProfile(user: $user) {
...AdminProfile
}
}
${ADMIN_PROFILE_FRAGMENT}
`
export const useProfileUpdateMutation = (
data?: MutationHookOptions<Data, Variables>
): MutationTuple<Data, Variables> => useMutation<Data, Variables>(MUTATION, data)

View File

@ -1,6 +1,7 @@
import { gql } from 'apollo-boost'
import { MutationHookOptions, MutationTuple, useMutation } from '@apollo/client'
import { gql } from '@apollo/client/core'
export interface RegisterMutationData {
interface Data {
tokens: {
access: string
refresh: string
@ -13,11 +14,11 @@ export interface RegisterUserData {
password: string
}
export interface RegisterMutationVariables {
interface Variables {
user: RegisterUserData
}
export const REGISTER_MUTATION = gql`
const MUTATION = gql`
mutation register($user: UserCreateInput!) {
tokens: authRegister(user: $user) {
access: accessToken
@ -25,3 +26,7 @@ export const REGISTER_MUTATION = gql`
}
}
`
export const useRegisterMutation = (
data?: MutationHookOptions<Data, Variables>
): MutationTuple<Data, Variables> => useMutation<Data, Variables>(MUTATION, data)

View File

@ -1,4 +1,4 @@
import { gql } from 'apollo-boost'
import { gql } from '@apollo/client/core'
export const SUBMISSION_FINISH_MUTATION = gql`
mutation start($submission: ID!, $field: SubmissionSetFieldInput!) {

View File

@ -1,4 +1,4 @@
import { gql } from 'apollo-boost'
import { gql } from '@apollo/client/core'
export interface SubmissionSetFieldMutationData {
submission: {

View File

@ -1,4 +1,4 @@
import { gql } from 'apollo-boost'
import { gql } from '@apollo/client/core'
export interface SubmissionStartMutationData {
submission: {

View File

@ -0,0 +1,24 @@
import { MutationHookOptions, MutationTuple, useMutation } from '@apollo/client'
import { gql } from '@apollo/client/core'
interface Data {
form: {
id
}
}
interface Variables {
id: string
}
const MUTATION = gql`
mutation delete($id: ID!) {
form: deleteUser(id: $id) {
id
}
}
`
export const useUserDeleteMutation = (
data?: MutationHookOptions<Data, Variables>
): MutationTuple<Data, Variables> => useMutation<Data, Variables>(MUTATION, data)

View File

@ -0,0 +1,25 @@
import { MutationHookOptions, MutationTuple, useMutation } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { ADMIN_USER_FRAGMENT, AdminUserFragment } from '../fragment/admin.user.fragment'
interface Data {
user: AdminUserFragment
}
interface Variables {
user: AdminUserFragment
}
const MUTATION = gql`
mutation update($user: UserUpdateInput!) {
form: updateUser(user: $user) {
...AdminUser
}
}
${ADMIN_USER_FRAGMENT}
`
export const useUserUpdateMutation = (
data?: MutationHookOptions<Data, Variables>
): MutationTuple<Data, Variables> => useMutation<Data, Variables>(MUTATION, data)

View File

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

View File

@ -1,55 +0,0 @@
import { gql } from 'apollo-boost'
export interface AdminPagerFormEntryAdminQueryData {
id: string
email: string
username: string
}
export interface AdminPagerFormEntryQueryData {
id: string
created: string
lastModified?: string
title: string
isLive: boolean
language: string
admin: AdminPagerFormEntryAdminQueryData
}
export interface AdminPagerFormQueryData {
pager: {
entries: AdminPagerFormEntryQueryData[]
total: number
limit: number
start: number
}
}
export interface AdminPagerFormQueryVariables {
start?: number
limit?: number
}
export const ADMIN_PAGER_FORM_QUERY = gql`
query pager($start: Int, $limit: Int) {
pager: listForms(start: $start, limit: $limit) {
entries {
id
created
lastModified
title
isLive
language
admin {
id
email
username
}
}
total
limit
start
}
}
`

View File

@ -1,98 +0,0 @@
import { gql } from 'apollo-boost'
export interface AdminPagerSubmissionFormFieldQueryData {
title: string
required: boolean
}
export interface AdminPagerSubmissionFormQueryData {
id: string
title: string
isLive: boolean
}
export interface AdminPagerSubmissionEntryFieldQueryData {
id: string
value: string
type: string
field?: AdminPagerSubmissionFormFieldQueryData
}
export interface AdminPagerSubmissionEntryQueryData {
id: string
created: string
lastModified?: string
percentageComplete: number
timeElapsed: number
geoLocation: {
country: string
city: string
}
device: {
type: string
name: string
}
fields: AdminPagerSubmissionEntryFieldQueryData[]
}
export interface AdminPagerSubmissionQueryData {
pager: {
entries: AdminPagerSubmissionEntryQueryData[]
total: number
limit: number
start: number
}
form: AdminPagerSubmissionFormQueryData
}
export interface AdminPagerSubmissionQueryVariables {
form: string
start?: number
limit?: number
}
export const ADMIN_PAGER_SUBMISSION_QUERY = gql`
query pager($form: ID!, $start: Int, $limit: Int) {
form: getFormById(id: $form) {
id
title
isLive
}
pager: listSubmissions(form: $form, start: $start, limit: $limit) {
entries {
id
created
lastModified
percentageComplete
timeElapsed
geoLocation {
country
city
}
device {
type
name
}
fields {
id
value
type
field {
title
required
}
}
}
total
limit
start
}
}
`

View File

@ -1,41 +0,0 @@
import { gql } from 'apollo-boost'
export interface AdminPagerUserEntryQueryData {
id: string
roles: string[]
verifiedEmail: boolean
email: string
created: string
}
export interface AdminPagerUserQueryData {
pager: {
entries: AdminPagerUserEntryQueryData[]
total: number
limit: number
start: number
}
}
export interface AdminPagerUserQueryVariables {
start?: number
limit?: number
}
export const ADMIN_PAGER_USER_QUERY = gql`
query pager($start: Int, $limit: Int) {
pager: listUsers(start: $start, limit: $limit) {
entries {
id
roles
verifiedEmail
email
created
}
total
limit
start
}
}
`

View File

@ -1,13 +1,12 @@
import { gql } from 'apollo-boost'
import { QueryHookOptions, QueryResult, useQuery } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { ADMIN_PROFILE_FRAGMENT, AdminProfileFragment } from '../fragment/admin.profile.fragment'
export interface AdminProfileQueryData {
interface Data {
user: AdminProfileFragment
}
export interface AdminProfileQueryVariables {}
export const ADMIN_PROFILE_QUERY = gql`
export const QUERY = gql`
query profile {
user: me {
...AdminProfile
@ -16,3 +15,7 @@ export const ADMIN_PROFILE_QUERY = gql`
${ADMIN_PROFILE_FRAGMENT}
`
export const useProfileQuery = (
options?: QueryHookOptions<Data, unknown>
): QueryResult<Data, unknown> => useQuery<Data, unknown>(QUERY, options)

View File

@ -1,4 +1,4 @@
import { gql } from 'apollo-boost'
import { gql } from '@apollo/client/core'
export interface AdminStatisticQueryData {
forms: {

View File

@ -1,4 +1,4 @@
import { gql } from 'apollo-boost'
import { gql } from '@apollo/client/core'
import { ADMIN_USER_FRAGMENT, AdminUserFragment } from '../fragment/admin.user.fragment'
export interface AdminUserQueryData {

View File

@ -0,0 +1,37 @@
import { QueryHookOptions, QueryResult, useQuery } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { FORM_PAGER_FRAGMENT, FormPagerFragment } from '../fragment/form.pager.fragment'
interface Data {
pager: {
entries: FormPagerFragment[]
total: number
limit: number
start: number
}
}
interface Variables {
start?: number
limit?: number
}
const QUERY = gql`
query pager($start: Int, $limit: Int) {
pager: listForms(start: $start, limit: $limit) {
entries {
...Form
}
total
limit
start
}
}
${FORM_PAGER_FRAGMENT}
`
export const useFormPagerQuery = (
options?: QueryHookOptions<Data, Variables>
): QueryResult<Data, Variables> => useQuery<Data, Variables>(QUERY, options)

View File

@ -0,0 +1,25 @@
import { QueryHookOptions, QueryResult, useQuery } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { FORM_PUBLIC_FRAGMENT, FormPublicFragment } from '../fragment/form.public.fragment'
interface Data {
form: FormPublicFragment
}
interface Variables {
id: string
}
const QUERY = gql`
query form($id: ID!) {
form: getFormById(id: $id) {
...Form
}
}
${FORM_PUBLIC_FRAGMENT}
`
export const useFormPublicQuery = (
options?: QueryHookOptions<Data, Variables>
): QueryResult<Data, Variables> => useQuery<Data, Variables>(QUERY, options)

View File

@ -1,16 +1,17 @@
import { gql } from 'apollo-boost'
import { QueryHookOptions, QueryResult, useQuery } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { FORM_FRAGMENT, FormFragment } from '../fragment/form.fragment'
export interface FormQueryData {
export interface Data {
form: FormFragment
}
export interface FormQueryVariables {
interface Variables {
id: string
}
export const FORM_QUERY = gql`
query form($id: ID!) {
const QUERY = gql`
query getFormById($id: ID!) {
form: getFormById(id: $id) {
...Form
}
@ -18,3 +19,7 @@ export const FORM_QUERY = gql`
${FORM_FRAGMENT}
`
export const useFormQuery = (
options?: QueryHookOptions<Data, Variables>
): QueryResult<Data, Variables> => useQuery<Data, Variables>(QUERY, options)

View File

@ -1,6 +1,7 @@
import { gql } from 'apollo-boost'
import { QueryHookOptions, QueryResult, useQuery } from '@apollo/client'
import { gql } from '@apollo/client/core'
export interface MeQueryData {
interface Data {
me: {
id: string
username: string
@ -8,8 +9,8 @@ export interface MeQueryData {
}
}
export const ME_QUERY = gql`
query {
const QUERY = gql`
query me {
me {
id
roles
@ -17,3 +18,6 @@ export const ME_QUERY = gql`
}
}
`
export const useMeQuery = (options?: QueryHookOptions<Data, unknown>): QueryResult<Data, unknown> =>
useQuery<Data, unknown>(QUERY, options)

View File

@ -1,6 +1,7 @@
import { gql } from 'apollo-boost'
import { QueryHookOptions, QueryResult, useQuery } from '@apollo/client'
import { gql } from '@apollo/client/core'
export interface SettingsQueryData {
interface Data {
disabledSignUp: {
value: boolean
}
@ -12,8 +13,8 @@ export interface SettingsQueryData {
}
}
export const SETTINGS_QUERY = gql`
query {
const QUERY = gql`
query settings {
disabledSignUp: getSetting(key: "SIGNUP_DISABLED") {
value: isTrue
}
@ -25,3 +26,7 @@ export const SETTINGS_QUERY = gql`
}
}
`
export const useSettingsQuery = (
options?: QueryHookOptions<Data, unknown>
): QueryResult<Data, unknown> => useQuery<Data, unknown>(QUERY, options)

View File

@ -0,0 +1,49 @@
import { QueryHookOptions, QueryResult, useQuery } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { useImperativeQuery } from '../../components/use.imerative.query'
import { FORM_PAGER_FRAGMENT, FormPagerFragment } from '../fragment/form.pager.fragment'
import { SUBMISSION_FRAGMENT, SubmissionFragment } from '../fragment/submission.fragment'
interface Data {
pager: {
entries: SubmissionFragment[]
total: number
limit: number
start: number
}
form: FormPagerFragment
}
interface Variables {
form: string
start?: number
limit?: number
}
const QUERY = gql`
query pager($form: ID!, $start: Int, $limit: Int) {
form: getFormById(id: $form) {
...Form
}
pager: listSubmissions(form: $form, start: $start, limit: $limit) {
entries {
...Submission
}
total
limit
start
}
}
${SUBMISSION_FRAGMENT}
${FORM_PAGER_FRAGMENT}
`
export const useSubmissionPagerQuery = (
options?: QueryHookOptions<Data, Variables>
): QueryResult<Data, Variables> => useQuery<Data, Variables>(QUERY, options)
export const useSubmissionPagerImperativeQuery = () => useImperativeQuery<Data, Variables>(QUERY)

View File

@ -0,0 +1,37 @@
import { QueryHookOptions, QueryResult, useQuery } from '@apollo/client'
import { gql } from '@apollo/client/core'
import { USER_PAGER_FRAGMENT, UserPagerFragment } from '../fragment/user.pager.fragment'
interface Data {
pager: {
entries: UserPagerFragment[]
total: number
limit: number
start: number
}
}
interface Variables {
start?: number
limit?: number
}
const QUERY = gql`
query pager($start: Int, $limit: Int) {
pager: listUsers(start: $start, limit: $limit) {
entries {
...User
}
total
limit
start
}
}
${USER_PAGER_FRAGMENT}
`
export const useUserPagerQuery = (
options?: QueryHookOptions<Data, Variables>
): QueryResult<Data, Variables> => useQuery<Data, Variables>(QUERY, options)

View File

@ -1,5 +1,6 @@
{
"admin": "Admin",
"profile": "Profile",
"checkingCredentials": "Checking Credentials",
"loadingCredentials": "Loading Credentials",
"login": "Login",

View File

@ -6,8 +6,12 @@ const version = p.version;
module.exports = withImages({
poweredByHeader: false,
publicRuntimeConfig: {
endpoint: process.env.API_HOST || '/graphql',
endpoint: process.env.ENDPOINT || '/graphql',
spa: !!process.env.SPA || false,
mainBackground: process.env.MAIN_BACKGROUND || '#8FA2A6'
},
serverRuntimeConfig: {
endpoint: process.env.SERVER_ENDPOINT || process.env.ENDPOINT || '/graphql',
},
env: {
version,

10
next.config.type.ts Normal file
View File

@ -0,0 +1,10 @@
export interface NextConfigType {
publicRuntimeConfig: {
endpoint: string
spa?: boolean
mainBackground?: string
}
serverRuntimeConfig: {
endpoint: string
}
}

View File

@ -1,71 +1,70 @@
{
"name": "ohmyform-react",
"version": "0.9.8",
"version": "1.0.0-alpha",
"license": "AGPL-3.0-or-later",
"scripts": {
"start:dev": "next dev -p 4000",
"build": "next build",
"lint": "eslint pages/ store/ components/ graphql/",
"type-check": "tsc --pretty",
"export": "cross-env SPA=1 next build && next export",
"start": "next start -p $PORT",
"translation:sort": "cross-env TS_NODE_TRANSPILE_ONLY=true ts-node-script locales/sort.ts",
"translation:missing": "cross-env TS_NODE_TRANSPILE_ONLY=true ts-node locales/missing.ts"
},
"dependencies": {
"@ant-design/icons": "^4.2.2",
"@apollo/client": "^3.2.0",
"@apollo/react-common": "^3.1.4",
"@apollo/react-hooks": "^4.0.0",
"@lifeomic/axios-fetch": "^1.4.2",
"antd": "^4.6.2",
"apollo-boost": "^0.4.9",
"axios": "^0.20.0",
"cross-env": "^7.0.2",
"dayjs": "^1.8.34",
"exceljs": "^4.2.0",
"graphql": "^15.3.0",
"i18next": "^19.7.0",
"i18next-browser-languagedetector": "^6.0.1",
"next": "9.5.3",
"next-images": "^1.4.1",
"@ant-design/icons": "^4.6.2",
"@apollo/client": "^3.3.15",
"@lifeomic/axios-fetch": "^2.0.0",
"antd": "^4.15.3",
"axios": "^0.21.1",
"cross-env": "^7.0.3",
"dayjs": "^1.10.4",
"exceljs": "^4.2.1",
"graphql": "^15.5.0",
"i18next": "^19.9.2",
"i18next-browser-languagedetector": "^6.1.0",
"isomorphic-fetch": "^3.0.0",
"next": "^10.2.0",
"next-images": "^1.7.0",
"next-redux-wrapper": "^6.0.2",
"react": "16.13.1",
"react-color": "^2.18.1",
"react-dom": "16.13.1",
"react": "^17.0.2",
"react-color": "^2.19.3",
"react-dom": "^17.0.2",
"react-github-button": "^0.1.11",
"react-i18next": "^11.7.2",
"react-i18next": "^11.8.15",
"react-icons": "^3.11.0",
"react-id-swiper": "^4.0.0",
"react-markdown": "^4.3.1",
"react-redux": "^7.2.1",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"react-redux": "^7.2.4",
"redux": "^4.1.0",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^2.3.0",
"sass": "^1.26.10",
"styled-components": "^5.1.1",
"swiper": "^6.1.2"
"sass": "^1.32.12",
"styled-components": "^5.2.3",
"swiper": "^6.5.8"
},
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/lifeomic__axios-fetch": "^1.4.0",
"@types/node": "^14.6.2",
"@types/node-fetch": "^2.5.7",
"@types/react": "^16.9.49",
"@types/styled-components": "^5.1.2",
"@types/swiper": "^5.4.0",
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"commander": "^6.1.0",
"eslint": "^7.8.1",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.6",
"eslint-plugin-react-hooks": "^4.1.0",
"@types/lifeomic__axios-fetch": "^1.5.0",
"@types/node": "^14.14.43",
"@types/node-fetch": "^2.5.10",
"@types/react": "^17.0.4",
"@types/styled-components": "^5.1.9",
"@types/swiper": "^5.4.2",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"commander": "^6.2.1",
"eslint": "^7.25.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"glob": "^7.1.6",
"lodash.merge": "^4.6.2",
"prettier": "^2.1.1",
"ts-node": "^9.0.0",
"typescript": "^4.0.2"
"ts-node": "^9.1.1",
"typescript": "^4.2.4"
}
}

View File

@ -1,19 +1,15 @@
import { ApolloProvider } from '@apollo/react-common'
import { buildAxiosFetch } from '@lifeomic/axios-fetch'
import { ApolloProvider } from '@apollo/client'
import 'antd/dist/antd.css'
import { ApolloProvider as ApolloHooksProvider } from '@apollo/react-hooks'
import ApolloClient from 'apollo-boost'
import 'assets/global.scss'
import 'assets/variables.scss'
import axios from 'axios'
import { authConfig } from 'components/with.auth'
import 'i18n'
import { AppInitialProps, AppProps } from 'next/app'
import { AppInitialProps } from 'next/app'
import getConfig from 'next/config'
import { AppType } from 'next/dist/next-server/lib/utils'
import Head from 'next/head'
import React from 'react'
import { wrapper } from 'store'
import getClient from '../graphql/client'
const { publicRuntimeConfig } = getConfig() as {
publicRuntimeConfig: {
@ -21,26 +17,14 @@ const { publicRuntimeConfig } = getConfig() as {
}
}
const client = new ApolloClient({
uri: publicRuntimeConfig.endpoint,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-explicit-any
fetch: buildAxiosFetch(axios as any) as any,
request: (operation): void => {
operation.setContext(authConfig())
},
})
const App: AppType = ({ Component, pageProps }) => {
return (
<ApolloProvider client={client}>
{/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */}
<ApolloHooksProvider client={client as any}>
<Head>
<title>OhMyForm</title>
<meta name="theme-color" content={'#4182e4'} />
</Head>
<Component {...pageProps} />
</ApolloHooksProvider>
<ApolloProvider client={getClient()}>
<Head>
<title>OhMyForm</title>
<meta name="theme-color" content={'#4182e4'} />
</Head>
<Component {...pageProps} />
</ApolloProvider>
)
}

View File

@ -1,4 +1,3 @@
import { useMutation, useQuery } from '@apollo/react-hooks'
import { Button, Form, Input, message, Tabs } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { cleanInput } from 'components/clean.input'
@ -6,75 +5,66 @@ import { BaseDataTab } from 'components/form/admin/base.data.tab'
import { DesignTab } from 'components/form/admin/design.tab'
import { EndPageTab } from 'components/form/admin/end.page.tab'
import { FieldsTab } from 'components/form/admin/fields.tab'
import { RespondentNotificationsTab } from 'components/form/admin/respondent.notifications.tab'
import { SelfNotificationsTab } from 'components/form/admin/self.notifications.tab'
import { NotificationsTab } from 'components/form/admin/notifications.tab'
import { StartPageTab } from 'components/form/admin/start.page.tab'
import Structure from 'components/structure'
import { Structure } from 'components/structure'
import { withAuth } from 'components/with.auth'
import {
AdminFormFieldFragment,
AdminFormFieldOptionKeysFragment,
} from 'graphql/fragment/admin.form.fragment'
import {
ADMIN_FORM_UPDATE_MUTATION,
AdminFormUpdateMutationData,
AdminFormUpdateMutationVariables,
} from 'graphql/mutation/admin.form.update.mutation'
import {
ADMIN_FORM_QUERY,
AdminFormQueryData,
AdminFormQueryVariables,
} from 'graphql/query/admin.form.query'
import { useFormUpdateMutation } from 'graphql/mutation/form.update.mutation'
import { NextPage } from 'next'
import Link from 'next/link'
import { useRouter } from 'next/router'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { HooksTab } from '../../../../components/form/admin/hooks.tab'
import {
FormFieldFragment,
FormFieldOptionKeysFragment,
} from '../../../../graphql/fragment/form.fragment'
import { Data, useFormQuery } from '../../../../graphql/query/form.query'
const Index: NextPage = () => {
const { t } = useTranslation()
const router = useRouter()
const [form] = useForm()
const [saving, setSaving] = useState(false)
const [fields, setFields] = useState<AdminFormFieldFragment[]>([])
const [update] = useMutation<AdminFormUpdateMutationData, AdminFormUpdateMutationVariables>(
ADMIN_FORM_UPDATE_MUTATION
)
const [fields, setFields] = useState<FormFieldFragment[]>([])
const [update] = useFormUpdateMutation()
const processNext = (next: AdminFormQueryData): AdminFormQueryData => {
next.form.fields = next.form.fields.map((field) => {
const keys: AdminFormFieldOptionKeysFragment = {}
const processNext = (next: Data) => {
return {
form: {
...next.form,
fields: next.form.fields.map((field) => {
const keys: FormFieldOptionKeysFragment = {}
field.options.forEach((option) => {
if (option.key) {
keys[option.key] = option.value
}
})
field.options.forEach((option) => {
if (option.key) {
keys[option.key] = option.value
}
})
field.options = field.options.filter((option) => !option.key)
field.optionKeys = keys
return field
})
return next
}
const { data, loading } = useQuery<AdminFormQueryData, AdminFormQueryVariables>(
ADMIN_FORM_QUERY,
{
variables: {
id: router.query.id as string,
},
onCompleted: (next) => {
next = processNext(next)
form.setFieldsValue(next)
setFields(next.form.fields)
return {
...field,
options: field.options.filter((option) => !option.key),
optionKeys: keys,
}
}),
},
}
)
}
const save = async (formData: AdminFormQueryData) => {
const { data, loading } = useFormQuery({
variables: {
id: router.query.id as string,
},
onCompleted: (next) => {
const processed = processNext(next)
form.setFieldsValue(processed)
setFields(processed.form.fields)
},
})
const save = async (formData: Data) => {
setSaving(true)
formData.form.fields = formData.form.fields
@ -89,6 +79,7 @@ const Index: NextPage = () => {
}
options.push({
id: null, // TODO improve this
value: optionKeys[key],
key,
})
@ -175,15 +166,9 @@ const Index: NextPage = () => {
/>
<BaseDataTab key={'base_data'} tab={t('form:baseDataTab')} />
<DesignTab key={'design'} tab={t('form:designTab')} />
<SelfNotificationsTab
key={'self_notifications'}
tab={t('form:selfNotificationsTab')}
fields={fields}
form={form}
/>
<RespondentNotificationsTab
key={'respondent_notifications'}
tab={t('form:respondentNotificationsTab')}
<NotificationsTab
key={'notifications'}
tab={t('form:notificationsTab')}
fields={fields}
form={form}
/>

View File

@ -1,4 +1,3 @@
import { useQuery } from '@apollo/react-hooks'
import { Button, Progress, Table } from 'antd'
import { PaginationProps } from 'antd/es/pagination'
import { ProgressProps } from 'antd/lib/progress'
@ -15,13 +14,9 @@ import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ExportSubmissionAction } from '../../../../components/form/admin/export.submission.action'
import { SubmissionValues } from '../../../../components/form/admin/submission.values'
import {
ADMIN_PAGER_SUBMISSION_QUERY,
AdminPagerSubmissionEntryQueryData,
AdminPagerSubmissionFormQueryData,
AdminPagerSubmissionQueryData,
AdminPagerSubmissionQueryVariables,
} from '../../../../graphql/query/admin.pager.submission.query'
import { FormPagerFragment } from '../../../../graphql/fragment/form.pager.fragment'
import { SubmissionFragment } from '../../../../graphql/fragment/submission.fragment'
import { useSubmissionPagerQuery } from '../../../../graphql/query/submission.pager.query'
const Submissions: NextPage = () => {
const { t } = useTranslation()
@ -29,12 +24,9 @@ const Submissions: NextPage = () => {
const [pagination, setPagination] = useState<PaginationProps>({
pageSize: 25,
})
const [form, setForm] = useState<AdminPagerSubmissionFormQueryData>()
const [entries, setEntries] = useState<AdminPagerSubmissionEntryQueryData[]>()
const { loading, refetch } = useQuery<
AdminPagerSubmissionQueryData,
AdminPagerSubmissionQueryVariables
>(ADMIN_PAGER_SUBMISSION_QUERY, {
const [form, setForm] = useState<FormPagerFragment>()
const [entries, setEntries] = useState<SubmissionFragment[]>()
const { loading, refetch } = useSubmissionPagerQuery({
variables: {
form: router.query.id as string,
limit: pagination.pageSize,
@ -50,10 +42,10 @@ const Submissions: NextPage = () => {
},
})
const columns: ColumnsType<AdminPagerSubmissionEntryQueryData> = [
const columns: ColumnsType<SubmissionFragment> = [
{
title: t('submission:progress'),
render(row: AdminPagerSubmissionEntryQueryData) {
render(_, row) {
const props: ProgressProps = {
status: 'active',
percent: Math.round(row.percentageComplete * 100),

View File

@ -1,31 +1,27 @@
import { useMutation } from '@apollo/react-hooks'
import { Button, Form, Input, message, Tabs } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { cleanInput } from 'components/clean.input'
import { BaseDataTab } from 'components/form/admin/base.data.tab'
import Structure from 'components/structure'
import { withAuth } from 'components/with.auth'
import { AdminFormQueryData } from 'graphql/query/admin.form.query'
import { FormFragment } from 'graphql/fragment/form.fragment'
import { useFormCreateMutation } from 'graphql/mutation/form.create.mutation'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
ADMIN_FORM_CREATE_MUTATION,
AdminFormCreateMutationData,
AdminFormCreateMutationVariables,
} from '../../../graphql/mutation/admin.form.create.mutation'
interface FormData {
form: FormFragment
}
const Create: NextPage = () => {
const { t } = useTranslation()
const router = useRouter()
const [form] = useForm()
const [form] = Form.useForm<FormData>()
const [saving, setSaving] = useState(false)
const [create] = useMutation<AdminFormCreateMutationData, AdminFormCreateMutationVariables>(
ADMIN_FORM_CREATE_MUTATION
)
const [create] = useFormCreateMutation()
const save = async (formData: AdminFormQueryData) => {
const save = async (formData: FormData) => {
setSaving(true)
try {

View File

@ -4,7 +4,6 @@ import {
GlobalOutlined,
UnorderedListOutlined,
} from '@ant-design/icons/lib'
import { useMutation, useQuery } from '@apollo/react-hooks'
import { Button, message, Popconfirm, Space, Table, Tag, Tooltip } from 'antd'
import { PaginationProps } from 'antd/es/pagination'
import { ColumnsType } from 'antd/lib/table/interface'
@ -13,23 +12,14 @@ import { FormIsLive } from 'components/form/admin/is.live'
import Structure from 'components/structure'
import { TimeAgo } from 'components/time.ago'
import { withAuth } from 'components/with.auth'
import {
ADMIN_PAGER_FORM_QUERY,
AdminPagerFormEntryAdminQueryData,
AdminPagerFormEntryQueryData,
AdminPagerFormQueryData,
AdminPagerFormQueryVariables,
} from 'graphql/query/admin.pager.form.query'
import { NextPage } from 'next'
import Link from 'next/link'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useWindowSize } from '../../../components/use.window.size'
import {
ADMIN_FORM_DELETE_MUTATION,
AdminFormDeleteMutationData,
AdminFormDeleteMutationVariables,
} from '../../../graphql/mutation/admin.form.delete.mutation'
import { FormPagerFragment } from '../../../graphql/fragment/form.pager.fragment'
import { useFormDeleteMutation } from '../../../graphql/mutation/form.delete.mutation'
import { useFormPagerQuery } from '../../../graphql/query/form.pager.query'
const Index: NextPage = () => {
const { t } = useTranslation()
@ -37,36 +27,30 @@ const Index: NextPage = () => {
const [pagination, setPagination] = useState<PaginationProps>({
pageSize: 25,
})
const [entries, setEntries] = useState<AdminPagerFormEntryQueryData[]>()
const { loading, refetch } = useQuery<AdminPagerFormQueryData, AdminPagerFormQueryVariables>(
ADMIN_PAGER_FORM_QUERY,
{
variables: {
limit: pagination.pageSize,
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
},
onCompleted: ({ pager }) => {
setPagination({
...pagination,
total: pager.total,
})
setEntries(pager.entries)
},
}
)
const [executeDelete] = useMutation<
AdminFormDeleteMutationData,
AdminFormDeleteMutationVariables
>(ADMIN_FORM_DELETE_MUTATION)
const [entries, setEntries] = useState<FormPagerFragment[]>()
const { loading, refetch, error } = useFormPagerQuery({
variables: {
limit: pagination.pageSize,
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
},
onCompleted: ({ pager }) => {
setPagination({
...pagination,
total: pager.total,
})
setEntries(pager.entries)
},
})
const [executeDelete] = useFormDeleteMutation()
const deleteForm = async (form: AdminFormDeleteMutationVariables) => {
const deleteForm = async (id: string) => {
try {
await executeDelete({
variables: {
id: form.id,
id,
},
})
const next = entries.filter((entry) => entry.id !== form.id)
const next = entries.filter((entry) => entry.id !== id)
if (next.length === 0) {
setPagination({ ...pagination, current: 1 })
} else {
@ -79,7 +63,7 @@ const Index: NextPage = () => {
}
}
const columns: ColumnsType<AdminPagerFormEntryQueryData> = [
const columns: ColumnsType<FormPagerFragment> = [
{
title: t('form:row.isLive'),
dataIndex: 'isLive',
@ -95,7 +79,7 @@ const Index: NextPage = () => {
{
title: t('form:row.admin'),
dataIndex: 'admin',
render(user: AdminPagerFormEntryAdminQueryData) {
render(_, { admin: user }) {
if (!user) {
return <Tag color={'red'} title={t('form:row.adminMissing')} />
}
@ -137,7 +121,7 @@ const Index: NextPage = () => {
{
title: t('form:row.menu'),
align: 'right',
render(row: AdminPagerFormEntryQueryData) {
render(_, row) {
return (
<Space direction={width < 600 ? 'vertical' : 'horizontal'}>
<Link href={'/admin/forms/[id]/submissions'} as={`/admin/forms/${row.id}/submissions`}>
@ -156,7 +140,7 @@ const Index: NextPage = () => {
<Popconfirm
title={t('form:confirmDelete')}
onConfirm={() => deleteForm(row)}
onConfirm={() => deleteForm(row.id)}
okText={t('form:deleteNow')}
okButtonProps={{ danger: true }}
>
@ -188,6 +172,7 @@ const Index: NextPage = () => {
<Button type={'primary'}>{t('form:new')}</Button>
</Link>,
]}
error={error?.message}
>
<Table
columns={columns}

View File

@ -1,4 +1,4 @@
import { useQuery } from '@apollo/react-hooks'
import { useQuery } from '@apollo/client'
import { Col, Row, Statistic } from 'antd'
import Structure from 'components/structure'
import { withAuth } from 'components/with.auth'

View File

@ -1,50 +1,57 @@
import { useMutation, useQuery } from '@apollo/react-hooks'
import { Button, Form, Input, message, Select } from 'antd'
import { Button, Divider, Form, Input, message, Select } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { cleanInput } from '../../components/clean.input'
import Structure from '../../components/structure'
import { withAuth } from '../../components/with.auth'
import {
ADMIN_PROFILE_UPDATE_MUTATION,
AdminProfileUpdateMutationData,
AdminProfileUpdateMutationVariables,
} from '../../graphql/mutation/admin.profile.update.mutation'
import {
ADMIN_PROFILE_QUERY,
AdminProfileQueryData,
AdminProfileQueryVariables,
} from '../../graphql/query/admin.profile.query'
import { AdminUserQueryData } from '../../graphql/query/admin.user.query'
import { useProfileUpdateMutation } from '../../graphql/mutation/profile.update.mutation'
import { useProfileQuery } from '../../graphql/query/admin.profile.query'
import { languages } from '../../i18n'
interface FormData {
user: {
id: string
username: string
email: string
language: string
firstName: string
lastName: string
}
password: string
confirm: string
}
const Profile: NextPage = () => {
const { t } = useTranslation()
const [form] = useForm()
const [form] = useForm<FormData>()
const [saving, setSaving] = useState(false)
const router = useRouter()
const { loading } = useQuery<AdminProfileQueryData, AdminProfileQueryVariables>(
ADMIN_PROFILE_QUERY,
{
onCompleted: (next) => {
form.setFieldsValue(next)
},
}
)
const { loading } = useProfileQuery({
onCompleted: (next) => {
form.setFieldsValue(next)
},
onError(e) {
void router.push('/')
},
})
const [update] = useMutation<AdminProfileUpdateMutationData, AdminProfileUpdateMutationVariables>(
ADMIN_PROFILE_UPDATE_MUTATION
)
const [update] = useProfileUpdateMutation()
const save = async (formData: AdminUserQueryData) => {
const save = async (data: FormData) => {
setSaving(true)
try {
const next = (
await update({
variables: cleanInput(formData),
variables: {
user: {
...data.user,
password: data.password && data.password === data.confirm ? data.password : undefined,
},
},
})
).data
@ -147,6 +154,46 @@ const Profile: NextPage = () => {
<Form.Item label={t('profile:lastName')} name={['user', 'lastName']}>
<Input />
</Form.Item>
<Divider />
<Form.Item
name="password"
label={t('profile:password')}
rules={[
{
min: 5,
message: t('validation:passwordMinLength'),
},
]}
>
<Input.Password />
</Form.Item>
<Form.Item noStyle shouldUpdate>
{() => (
<Form.Item
name="confirm"
label={t('profile:confirmPassword')}
rules={[
{
required: Boolean(form.getFieldValue('password')),
message: t('validation:passwordConfirmRequired'),
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve()
}
return Promise.reject(new Error(t('validation:passwordConfirmMismatch')))
},
}),
]}
>
<Input.Password />
</Form.Item>
)}
</Form.Item>
</Form>
</Structure>
)

View File

@ -1,4 +1,4 @@
import { useMutation, useQuery } from '@apollo/react-hooks'
import { useQuery } from '@apollo/client'
import { Button, Form, Input, message, Tabs } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import Structure from 'components/structure'
@ -9,11 +9,7 @@ import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { cleanInput } from '../../../../components/clean.input'
import { BaseDataTab } from '../../../../components/user/admin/base.data.tab'
import {
ADMIN_USER_UPDATE_MUTATION,
AdminUserUpdateMutationData,
AdminUserUpdateMutationVariables,
} from '../../../../graphql/mutation/admin.user.update.mutation'
import { useUserUpdateMutation } from '../../../../graphql/mutation/user.update.mutation'
import {
ADMIN_USER_QUERY,
AdminUserQueryData,
@ -38,9 +34,7 @@ const Index: NextPage = () => {
}
)
const [update] = useMutation<AdminUserUpdateMutationData, AdminUserUpdateMutationVariables>(
ADMIN_USER_UPDATE_MUTATION
)
const [update] = useUserUpdateMutation()
const save = async (formData: AdminUserQueryData) => {
setSaving(true)

View File

@ -1,5 +1,4 @@
import { DeleteOutlined, EditOutlined } from '@ant-design/icons/lib'
import { useMutation, useQuery } from '@apollo/react-hooks'
import { Button, message, Popconfirm, Space, Table, Tag } from 'antd'
import { PaginationProps } from 'antd/es/pagination'
import { ColumnsType } from 'antd/lib/table/interface'
@ -12,17 +11,9 @@ import { useTranslation } from 'react-i18next'
import { DateTime } from '../../../components/date.time'
import { useWindowSize } from '../../../components/use.window.size'
import { UserRole } from '../../../components/user/role'
import {
ADMIN_USER_DELETE_MUTATION,
AdminUserDeleteMutationData,
AdminUserDeleteMutationVariables,
} from '../../../graphql/mutation/admin.user.delete.mutation'
import {
ADMIN_PAGER_USER_QUERY,
AdminPagerUserEntryQueryData,
AdminPagerUserQueryData,
AdminPagerUserQueryVariables,
} from '../../../graphql/query/admin.pager.user.query'
import { UserPagerFragment } from '../../../graphql/fragment/user.pager.fragment'
import { useUserDeleteMutation } from '../../../graphql/mutation/user.delete.mutation'
import { useUserPagerQuery } from '../../../graphql/query/user.pager.query'
const Index: NextPage = () => {
const { width } = useWindowSize()
@ -30,36 +21,30 @@ const Index: NextPage = () => {
const [pagination, setPagination] = useState<PaginationProps>({
pageSize: 10,
})
const [entries, setEntries] = useState<AdminPagerUserEntryQueryData[]>()
const { loading, refetch } = useQuery<AdminPagerUserQueryData, AdminPagerUserQueryVariables>(
ADMIN_PAGER_USER_QUERY,
{
variables: {
limit: pagination.pageSize,
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
},
onCompleted: ({ pager }) => {
setPagination({
...pagination,
total: pager.total,
})
setEntries(pager.entries)
},
}
)
const [executeDelete] = useMutation<
AdminUserDeleteMutationData,
AdminUserDeleteMutationVariables
>(ADMIN_USER_DELETE_MUTATION)
const [entries, setEntries] = useState<UserPagerFragment[]>()
const { loading, refetch, error } = useUserPagerQuery({
variables: {
limit: pagination.pageSize,
start: Math.max(0, pagination.current - 1) * pagination.pageSize || 0,
},
onCompleted: ({ pager }) => {
setPagination({
...pagination,
total: pager.total,
})
setEntries(pager.entries)
},
})
const [executeDelete] = useUserDeleteMutation()
const deleteUser = async (form: AdminPagerUserEntryQueryData) => {
const deleteUser = async (id: string) => {
try {
await executeDelete({
variables: {
id: form.id,
id,
},
})
const next = entries.filter((entry) => entry.id !== form.id)
const next = entries.filter((entry) => entry.id !== id)
if (next.length === 0) {
setPagination({ ...pagination, current: 1 })
} else {
@ -71,7 +56,7 @@ const Index: NextPage = () => {
}
}
const columns: ColumnsType<AdminPagerUserEntryQueryData> = [
const columns: ColumnsType<UserPagerFragment> = [
{
title: t('user:row.roles'),
dataIndex: 'roles',
@ -82,7 +67,7 @@ const Index: NextPage = () => {
},
{
title: t('user:row.email'),
render(row: AdminPagerUserEntryQueryData) {
render(_, row) {
return <Tag color={row.verifiedEmail ? 'lime' : 'volcano'}>{row.email}</Tag>
},
},
@ -97,7 +82,7 @@ const Index: NextPage = () => {
{
title: t('user:row.menu'),
align: 'right',
render(row: AdminPagerUserEntryQueryData) {
render(_, row) {
return (
<Space direction={width < 600 ? 'vertical' : 'horizontal'}>
<Link href={'/admin/users/[id]'} as={`/admin/users/${row.id}`}>
@ -108,7 +93,7 @@ const Index: NextPage = () => {
<Popconfirm
title={t('user:confirmDelete')}
onConfirm={() => deleteUser(row)}
onConfirm={() => deleteUser(row.id)}
okText={t('user:deleteNow')}
okButtonProps={{ danger: true }}
>
@ -128,6 +113,7 @@ const Index: NextPage = () => {
loading={loading}
breadcrumbs={[{ href: '/admin', name: t('admin:home') }]}
padded={false}
error={error?.message}
>
<Table
columns={columns}

View File

@ -1,10 +1,8 @@
import { useQuery } from '@apollo/react-hooks'
import { Modal } from 'antd'
import { ErrorPage } from 'components/error.page'
import { Field } from 'components/form/field'
import { FormPage } from 'components/form/page'
import { LoadingPage } from 'components/loading.page'
import { FORM_QUERY, FormQueryData, FormQueryVariables } from 'graphql/query/form.query'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
import React, { useEffect, useState } from 'react'
@ -14,6 +12,7 @@ import { ReactIdSwiperProps } from 'react-id-swiper/lib/types'
import * as OriginalSwiper from 'swiper'
import { Omf } from '../../../components/omf'
import { useSubmission } from '../../../components/use.submission'
import { useFormPublicQuery } from '../../../graphql/query/form.public.query'
interface Props {
id: string
@ -26,7 +25,7 @@ const Index: NextPage<Props> = () => {
const [swiper, setSwiper] = useState<OriginalSwiper.default>(null)
const submission = useSubmission(id)
const { loading, data, error } = useQuery<FormQueryData, FormQueryVariables>(FORM_QUERY, {
const { loading, data, error } = useFormPublicQuery({
variables: {
id,
},
@ -74,7 +73,7 @@ const Index: NextPage<Props> = () => {
return (
<div
style={{
background: design.colors.backgroundColor,
background: design.colors.background,
}}
>
<Omf />

View File

@ -1,19 +1,15 @@
import { Layout } from 'antd'
import { AuthFooter } from 'components/auth/footer'
import { NextPage } from 'next'
import { GetStaticProps, NextPage } from 'next'
import getConfig from 'next/config'
import { useRouter } from 'next/router'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { LoadingPage } from '../components/loading.page'
import { Omf } from '../components/omf'
import { NextConfigType } from '../next.config.type'
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { publicRuntimeConfig } = getConfig() as {
publicRuntimeConfig: {
spa: boolean
}
}
const { publicRuntimeConfig } = getConfig() as NextConfigType
const Index: NextPage = () => {
const router = useRouter()
@ -56,7 +52,7 @@ const Index: NextPage = () => {
<Layout
style={{
height: '100vh',
background: '#437fdc',
background: publicRuntimeConfig.mainBackground,
}}
>
<Omf />
@ -76,4 +72,11 @@ const Index: NextPage = () => {
)
}
export const getStaticProps: GetStaticProps = async () => {
return {
revalidate: 10,
props: {},
}
}
export default Index

View File

@ -1,4 +1,4 @@
import { useMutation, useQuery } from '@apollo/react-hooks'
import { useMutation } from '@apollo/client'
import { Alert, Button, Form, Input, message } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { AuthFooter } from 'components/auth/footer'
@ -16,7 +16,7 @@ import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ReactMarkdown from 'react-markdown'
import { Omf } from '../../components/omf'
import { SETTINGS_QUERY, SettingsQueryData } from '../../graphql/query/settings.query'
import { useSettingsQuery } from '../../graphql/query/settings.query'
import scss from './index.module.scss'
const Index: NextPage = () => {
@ -25,7 +25,7 @@ const Index: NextPage = () => {
const router = useRouter()
const [loading, setLoading] = useState(false)
const [login] = useMutation<LoginMutationData, LoginMutationVariables>(LOGIN_MUTATION)
const { data } = useQuery<SettingsQueryData>(SETTINGS_QUERY)
const { data } = useSettingsQuery()
const finish = async (data: LoginMutationVariables) => {
setLoading(true)

View File

@ -1,15 +1,9 @@
import { useMutation, useQuery } from '@apollo/react-hooks'
import { Button, Form, Input, message } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { AuthFooter } from 'components/auth/footer'
import { AuthLayout } from 'components/auth/layout'
import { setAuth } from 'components/with.auth'
import {
REGISTER_MUTATION,
RegisterMutationData,
RegisterMutationVariables,
RegisterUserData,
} from 'graphql/mutation/register.mutation'
import { RegisterUserData, useRegisterMutation } from 'graphql/mutation/register.mutation'
import { NextPage } from 'next'
import Link from 'next/link'
import { useRouter } from 'next/router'
@ -17,7 +11,7 @@ import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ErrorPage } from '../components/error.page'
import { Omf } from '../components/omf'
import { SETTINGS_QUERY, SettingsQueryData } from '../graphql/query/settings.query'
import { useSettingsQuery } from '../graphql/query/settings.query'
import scss from './register.module.scss'
const Register: NextPage = () => {
@ -25,9 +19,9 @@ const Register: NextPage = () => {
const [form] = useForm()
const router = useRouter()
const [loading, setLoading] = useState(false)
const { data } = useQuery<SettingsQueryData>(SETTINGS_QUERY)
const { data } = useSettingsQuery()
const [register] = useMutation<RegisterMutationData, RegisterMutationVariables>(REGISTER_MUTATION)
const [register] = useRegisterMutation()
const finish = async (data: RegisterUserData) => {
setLoading(true)

View File

@ -1,423 +1,420 @@
# This file was generated based on ".graphqlconfig". Do not edit manually.
schema {
query: Query
mutation: Mutation
}
interface Notification {
enabled: Boolean!
htmlTemplate: String
subject: String
query: Query
mutation: Mutation
}
type AuthToken {
accessToken: String!
refreshToken: String!
accessToken: String!
refreshToken: String!
}
type Button {
action: String
activeColor: String
bgColor: String
color: String
text: String
url: String
action: String
activeColor: String
bgColor: String
color: String
id: ID!
text: String
url: String
}
type Colors {
answerColor: String!
backgroundColor: String!
buttonActiveColor: String!
buttonColor: String!
buttonTextColor: String!
questionColor: String!
answer: String!
background: String!
button: String!
buttonActive: String!
buttonText: String!
question: String!
}
type Deleted {
id: String!
id: String!
}
type Design {
colors: Colors!
font: String
colors: Colors!
font: String
}
type Device {
name: String!
type: String!
language: String
name: String!
type: String!
}
type Form {
admin: User
created: DateTime!
design: Design!
endPage: Page!
fields: [FormField!]!
hooks: [FormHook!]!
id: ID!
isLive: Boolean!
language: String!
lastModified: DateTime
respondentNotifications: RespondentNotificationsModel!
selfNotifications: SelfNotificationsModel!
showFooter: Boolean!
startPage: Page!
title: String!
admin: User
created: DateTime!
design: Design!
endPage: Page!
fields: [FormField!]!
hooks: [FormHook!]!
id: ID!
isLive: Boolean!
language: String!
lastModified: DateTime
notifications: [FormNotification!]!
showFooter: Boolean!
startPage: Page!
title: String!
}
type FormField {
description: String!
id: ID!
logicJump: LogicJump!
options: [FormFieldOption!]!
rating: FormFieldRating
required: Boolean!
slug: String
title: String!
type: String!
value: String!
description: String!
id: ID!
logic: [FormFieldLogic!]!
options: [FormFieldOption!]!
rating: FormFieldRating
required: Boolean!
slug: String
title: String!
type: String!
value: String!
}
type FormFieldLogic {
action: String!
disable: Boolean
enabled: Boolean!
formula: String
id: ID!
jumpTo: ID
require: Boolean
visible: Boolean
}
type FormFieldOption {
key: String
title: String
value: String!
id: ID!
key: String
title: String
value: String!
}
type FormFieldRating {
shape: String
steps: Int
shape: String
steps: Int
}
type FormHook {
enabled: Boolean!
format: String
id: ID!
url: String
enabled: Boolean!
format: String
id: ID!
url: String
}
type FormNotification {
enabled: Boolean!
fromEmail: String
fromField: String
htmlTemplate: String
id: ID!
subject: String
toEmail: String
toField: String
}
type FormPager {
entries: [Form!]!
limit: Int!
start: Int!
total: Int!
}
type FormStatistic {
total: Int!
total: Int!
}
type GeoLocation {
city: String
country: String
}
type LogicJump {
enabled: Boolean!
expressionString: String
fieldA: ID
jumpTo: ID
valueB: String
city: String
country: String
}
type Mutation {
authLogin(password: String!, username: String!): AuthToken!
authRegister(user: UserCreateInput!): AuthToken!
createForm(form: FormCreateInput!): Form!
deleteForm(id: ID!): Deleted!
deleteUser(id: ID!): Deleted!
submissionSetField(field: SubmissionSetFieldInput!, submission: ID!): SubmissionProgress!
submissionStart(form: ID!, submission: SubmissionStartInput!): SubmissionProgress!
updateForm(form: FormUpdateInput!): Form!
updateProfile(user: ProfileUpdateInput!): Profile!
updateUser(user: UserUpdateInput!): User!
authLogin(password: String!, username: String!): AuthToken!
authRegister(user: UserCreateInput!): AuthToken!
createForm(form: FormCreateInput!): Form!
deleteForm(id: ID!): Deleted!
deleteUser(id: ID!): Deleted!
submissionSetField(field: SubmissionSetFieldInput!, submission: ID!): SubmissionProgress!
submissionStart(form: ID!, submission: SubmissionStartInput!): SubmissionProgress!
updateForm(form: FormUpdateInput!): Form!
updateProfile(user: ProfileUpdateInput!): Profile!
updateUser(user: UserUpdateInput!): User!
}
type Page {
buttonText: String
buttons: [Button!]!
paragraph: String
show: Boolean!
title: String
}
type PagerForm {
entries: [Form!]!
limit: Int!
start: Int!
total: Int!
}
type PagerSetting {
entries: [Setting!]!
limit: Int!
start: Int!
total: Int!
}
type PagerSubmission {
entries: [Submission!]!
limit: Int!
start: Int!
total: Int!
}
type PagerUser {
entries: [User!]!
limit: Int!
start: Int!
total: Int!
buttonText: String
buttons: [Button!]!
id: ID!
paragraph: String
show: Boolean!
title: String
}
type Profile {
created: DateTime!
email: String!
firstName: String
id: ID!
language: String!
lastModified: DateTime
lastName: String
roles: [String!]!
username: String!
verifiedEmail: Boolean!
created: DateTime!
email: String!
firstName: String
id: ID!
language: String!
lastModified: DateTime
lastName: String
roles: [String!]!
username: String!
verifiedEmail: Boolean!
}
type Query {
getFormById(id: ID!): Form!
getFormStatistic: FormStatistic!
getSetting(key: ID!): Setting!
getSettings: PagerSetting!
getSubmissionStatistic: SubmissionStatistic!
getUserById(id: ID!): User!
getUserStatistic: UserStatistic!
listForms(limit: Int = 50, start: Int = 0): PagerForm!
listSubmissions(form: ID!, limit: Int = 50, start: Int = 0): PagerSubmission!
listUsers(limit: Int = 50, start: Int = 0): PagerUser!
me: Profile!
status: Version!
}
type RespondentNotificationsModel implements Notification {
enabled: Boolean!
fromEmail: String
htmlTemplate: String
subject: String
toField: String
}
type SelfNotificationsModel implements Notification {
enabled: Boolean!
fromField: String
htmlTemplate: String
subject: String
toEmail: String
getFormById(id: ID!): Form!
getFormStatistic: FormStatistic!
getSetting(key: ID!): Setting!
getSettings: SettingPager!
getSubmissionStatistic: SubmissionStatistic!
getUserById(id: ID!): User!
getUserStatistic: UserStatistic!
listForms(limit: Int = 50, start: Int = 0): FormPager!
listSubmissions(form: ID!, limit: Int = 50, start: Int = 0): SubmissionPager!
listUsers(limit: Int = 50, start: Int = 0): UserPager!
me: Profile!
status: Version!
}
type Setting {
isFalse: Boolean!
isTrue: Boolean!
key: ID!
value: String
isFalse: Boolean!
isTrue: Boolean!
key: ID!
value: String
}
type SettingPager {
entries: [Setting!]!
limit: Int!
start: Int!
total: Int!
}
type Submission {
created: DateTime!
device: Device!
fields: [SubmissionField!]!
geoLocation: GeoLocation!
id: ID!
ipAddr: String!
lastModified: DateTime
percentageComplete: Float!
timeElapsed: Float!
created: DateTime!
device: Device!
fields: [SubmissionField!]!
geoLocation: GeoLocation!
id: ID!
ipAddr: String!
lastModified: DateTime
percentageComplete: Float!
timeElapsed: Float!
}
type SubmissionField {
field: FormField
id: ID!
type: String!
value: String!
field: FormField
id: ID!
type: String!
value: String!
}
type SubmissionPager {
entries: [Submission!]!
limit: Int!
start: Int!
total: Int!
}
type SubmissionProgress {
created: DateTime!
id: ID!
lastModified: DateTime
percentageComplete: Float!
timeElapsed: Float!
created: DateTime!
id: ID!
lastModified: DateTime
percentageComplete: Float!
timeElapsed: Float!
}
type SubmissionStatistic {
total: Int!
total: Int!
}
type User {
created: DateTime!
email: String!
firstName: String
id: ID!
language: String!
lastModified: DateTime
lastName: String
roles: [String!]!
username: String!
verifiedEmail: Boolean!
created: DateTime!
email: String!
firstName: String
id: ID!
language: String!
lastModified: DateTime
lastName: String
roles: [String!]!
username: String!
verifiedEmail: Boolean!
}
type UserPager {
entries: [User!]!
limit: Int!
start: Int!
total: Int!
}
type UserStatistic {
total: Int!
total: Int!
}
type Version {
version: String!
version: String!
}
input ButtonInput {
action: String
activeColor: String
bgColor: String
color: String
text: String
url: String
action: String
activeColor: String
bgColor: String
color: String
id: ID
text: String
url: String
}
input ColorsInput {
answerColor: String!
backgroundColor: String!
buttonActiveColor: String!
buttonColor: String!
buttonTextColor: String!
questionColor: String!
answer: String!
background: String!
button: String!
buttonActive: String!
buttonText: String!
question: String!
}
input DesignInput {
colors: ColorsInput!
font: String
colors: ColorsInput!
font: String
}
input DeviceInput {
name: String!
type: String!
language: String
name: String!
type: String!
}
input FormCreateInput {
isLive: Boolean
language: String!
showFooter: Boolean
title: String!
isLive: Boolean
language: String!
showFooter: Boolean
title: String!
}
input FormFieldInput {
description: String!
id: ID!
logicJump: LogicJumpInput
options: [FormFieldOptionInput!]
rating: FormFieldRatingInput
required: Boolean!
slug: String
title: String!
type: String!
value: String!
description: String!
disabled: Boolean
id: ID!
logic: [FormFieldLogicInput!]
options: [FormFieldOptionInput!]
rating: FormFieldRatingInput
required: Boolean!
slug: String
title: String!
type: String!
value: String!
}
input FormFieldLogicInput {
action: String
disable: Boolean
enabled: Boolean
formula: String
id: ID
jumpTo: ID
require: Boolean
visible: Boolean
}
input FormFieldOptionInput {
key: String
title: String
value: String!
id: ID
key: String
title: String
value: String!
}
input FormFieldRatingInput {
shape: String
steps: Int
shape: String
steps: Int
}
input FormHookInput {
enabled: Boolean!
format: String
id: ID!
url: String
enabled: Boolean!
format: String
id: ID!
url: String
}
input FormNotificationInput {
enabled: Boolean!
fromEmail: String
fromField: String
htmlTemplate: String
id: ID
subject: String
toEmail: String
toField: String
}
input FormUpdateInput {
design: DesignInput
endPage: PageInput
fields: [FormFieldInput!]
hooks: [FormHookInput!]
id: ID!
isLive: Boolean
language: String
respondentNotifications: RespondentNotificationsInput
selfNotifications: SelfNotificationsInput
showFooter: Boolean
startPage: PageInput
title: String
}
input LogicJumpInput {
enabled: Boolean
expressionString: String
fieldA: ID
jumpTo: ID
valueB: String
design: DesignInput
endPage: PageInput
fields: [FormFieldInput!]
hooks: [FormHookInput!]
id: ID!
isLive: Boolean
language: String
notifications: [FormNotificationInput!]
showFooter: Boolean
startPage: PageInput
title: String
}
input PageInput {
buttonText: String
buttons: [ButtonInput!]!
paragraph: String
show: Boolean!
title: String
buttonText: String
buttons: [ButtonInput!]!
id: ID
paragraph: String
show: Boolean!
title: String
}
input ProfileUpdateInput {
email: String
firstName: String
id: ID!
language: String
lastName: String
password: String
username: String
}
input RespondentNotificationsInput {
enabled: Boolean!
fromEmail: String
htmlTemplate: String
subject: String
toField: String
}
input SelfNotificationsInput {
enabled: Boolean!
fromField: String
htmlTemplate: String
subject: String
toEmail: String
email: String
firstName: String
id: ID!
language: String
lastName: String
password: String
username: String
}
input SubmissionSetFieldInput {
data: String!
field: ID!
token: String!
data: String!
field: ID!
token: String!
}
input SubmissionStartInput {
device: DeviceInput!
token: String!
device: DeviceInput!
token: String!
}
input UserCreateInput {
email: String!
firstName: String
language: String
lastName: String
password: String!
username: String!
email: String!
firstName: String
language: String
lastName: String
password: String!
username: String!
}
input UserUpdateInput {
email: String
firstName: String
id: ID!
language: String
lastName: String
password: String
roles: [String!]
username: String
email: String
firstName: String
id: ID!
language: String
lastName: String
password: String
roles: [String!]
username: String
}

5434
yarn.lock

File diff suppressed because it is too large Load Diff