add license and more frontend field types

This commit is contained in:
Michael Schramm 2020-05-30 11:56:48 +02:00
parent ec0f6e9572
commit 34d154b4dd
50 changed files with 8038 additions and 96 deletions

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
## License
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,4 +1,5 @@
@import "variables";
@import "node_modules/swiper/swiper.scss";
:root {
--backgroundColor: #{$background-color};
@ -26,3 +27,11 @@
.ant-spin-nested-loading > div > .ant-spin {
max-height: unset;
}
.swiper-container {
height: 100vh;
.swiper-wrapper {
position: fixed
}
}

View File

@ -18,6 +18,7 @@ export const DesignTab: React.FC<TabPaneProps> = props => {
{name: 'questionColor', label: 'Question Color'},
{name: 'answerColor', label: 'Answer Color'},
{name: 'buttonColor', label: 'Button Color'},
{name: 'buttonActiveColor', label: 'Button Active Color'},
{name: 'buttonTextColor', label: 'Button Text Color'},
].map(({label, name}) => (
<Form.Item key={name} label={label} name={['form', 'design', 'colors', name]}>

View File

@ -55,7 +55,14 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
]}
>
<Form.Item label={'Url'} name={[field.key, 'url']} labelCol={{ span: 6 }}>
<Form.Item
label={'Url'}
name={[field.key, 'url']}
rules={[
{type: 'url', message: 'Must be a valid url'}
]}
labelCol={{ span: 6 }}
>
<Input />
</Form.Item>
<Form.Item label={'Action'} name={[field.key, 'action']} labelCol={{ span: 6 }}>
@ -67,6 +74,9 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
<Form.Item label={'Background Color'} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
<InputColor />
</Form.Item>
<Form.Item label={'Active Color'} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}>
<InputColor />
</Form.Item>
<Form.Item label={'Color'} name={[field.key, 'color']} labelCol={{ span: 6 }}>
<InputColor />
</Form.Item>

View File

@ -4,31 +4,8 @@ import {FormInstance} from 'antd/lib/form'
import {FieldData} from 'rc-field-form/lib/interface'
import React, {useEffect, useState} from 'react'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
import {DateType} from './types/date.type'
import {DropdownType} from './types/dropdown.type'
import {EmailType} from './types/email.type'
import {HiddenType} from './types/hidden.type'
import {LinkType} from './types/link.type'
import {NumberType} from './types/number.type'
import {RadioType} from './types/radio.type'
import {RatingType} from './types/rating.type'
import {adminTypes} from './types'
import {TextType} from './types/text.type'
import {TextareaType} from './types/textarea.type'
import {YesNoType} from './types/yes_no.type'
export const availableTypes = {
'textfield': TextType,
'date': DateType,
'email': EmailType,
'textarea': TextareaType,
'link': LinkType,
'dropdown': DropdownType,
'rating': RatingType,
'radio': RadioType,
'hidden': HiddenType,
'yes_no': YesNoType,
'number': NumberType,
}
interface Props {
form: FormInstance
@ -50,7 +27,7 @@ export const FieldCard: React.FC<Props> = props => {
} = props
const type = form.getFieldValue(['form', 'fields', field.name as string, 'type'])
const TypeComponent: React.FC<any> = availableTypes[type] || TextType
const TypeComponent = adminTypes[type] || TextType
const [nextTitle, setNextTitle] = useState(form.getFieldValue(['form', 'fields', field.name as string, 'title']))
@ -120,6 +97,7 @@ export const FieldCard: React.FC<Props> = props => {
name={[field.name as string, 'required']}
labelCol={{ span: 6 }}
valuePropName={'checked'}
extra={type === 'hidden' && 'If required, default value must be set to enable users to submit form!'}
>
<Checkbox />
</Form.Item>

View File

@ -4,7 +4,8 @@ import {FormInstance} from 'antd/lib/form'
import {TabPaneProps} from 'antd/lib/tabs'
import React, {useCallback, useState} from 'react'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
import {availableTypes, FieldCard} from './field.card'
import {FieldCard} from './field.card'
import {adminTypes} from './types'
interface Props extends TabPaneProps {
form: FormInstance
@ -40,7 +41,7 @@ export const FieldsTab: React.FC<Props> = props => {
}}
>
<Select value={nextType} onChange={e => setNextType(e)} style={{ minWidth: 200 }}>
{Object.keys(availableTypes).map(type => <Select.Option value={type} key={type}>{type}</Select.Option> )}
{Object.keys(adminTypes).map(type => <Select.Option value={type} key={type}>{type}</Select.Option> )}
</Select>
<Button
type="dashed"

View File

@ -55,7 +55,14 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
]}
>
<Form.Item label={'Url'} name={[field.key, 'url']} labelCol={{ span: 6 }}>
<Form.Item
label={'Url'}
name={[field.key, 'url']}
rules={[
{type: 'url', message: 'Must be a valid url'}
]}
labelCol={{ span: 6 }}
>
<Input />
</Form.Item>
<Form.Item label={'Action'} name={[field.key, 'action']} labelCol={{ span: 6 }}>
@ -67,6 +74,9 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
<Form.Item label={'Background Color'} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
<InputColor />
</Form.Item>
<Form.Item label={'Active Color'} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}>
<InputColor />
</Form.Item>
<Form.Item label={'Color'} name={[field.key, 'color']} labelCol={{ span: 6 }}>
<InputColor />
</Form.Item>

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import {DatePicker, Form} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const DateType: React.FC<Props> = props => {
export const DateType: React.FC<AdminFieldTypeProps> = props => {
return (
<div>
<Form.Item
@ -13,21 +10,21 @@ export const DateType: React.FC<Props> = props => {
name={[props.field.name, 'value']}
labelCol={{ span: 6 }}
>
<Input type={'date'} />
<DatePicker />
</Form.Item>
<Form.Item
label={'Min Date'}
name={[props.field.name, 'value']}
name={[props.field.name, 'min']}
labelCol={{ span: 6 }}
>
<Input />
<DatePicker />
</Form.Item>
<Form.Item
label={'Max Date'}
name={[props.field.name, 'value']}
name={[props.field.name, 'max']}
labelCol={{ span: 6 }}
>
<Input />
<DatePicker />
</Form.Item>
</div>
)

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const DropdownType: React.FC<Props> = props => {
export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
// TODO add dropdown options
return (
<div>

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const EmailType: React.FC<Props> = props => {
export const EmailType: React.FC<AdminFieldTypeProps> = props => {
return (
<div>
<Form.Item

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const HiddenType: React.FC<Props> = props => {
export const HiddenType: React.FC<AdminFieldTypeProps> = props => {
return (
<div>
<Form.Item

View File

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

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const LinkType: React.FC<Props> = props => {
export const LinkType: React.FC<AdminFieldTypeProps> = props => {
return (
<div>
<Form.Item

View File

@ -1,11 +1,8 @@
import {Form, InputNumber} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const NumberType: React.FC<Props> = props => {
export const NumberType: React.FC<AdminFieldTypeProps> = props => {
return (
<div>
<Form.Item

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const RadioType: React.FC<Props> = props => {
export const RadioType: React.FC<AdminFieldTypeProps> = props => {
// TODO Add radio support
return (

View File

@ -1,13 +1,9 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const RatingType: React.FC<Props> = props => {
export const RatingType: React.FC<AdminFieldTypeProps> = props => {
// TODO add ratings
return (
<div>
<Form.Item

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const TextType: React.FC<Props> = props => {
export const TextType: React.FC<AdminFieldTypeProps> = props => {
return (
<div>
<Form.Item

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const TextareaType: React.FC<Props> = props => {
export const TextareaType: React.FC<AdminFieldTypeProps> = props => {
return (
<div>
<Form.Item

View File

@ -0,0 +1,3 @@
export interface AdminFieldTypeProps {
field: any
}

View File

@ -1,11 +1,8 @@
import {Form, Input} from 'antd'
import React from 'react'
import {AdminFieldTypeProps} from './type.props'
interface Props {
field: any
}
export const YesNoType: React.FC<Props> = props => {
export const YesNoType: React.FC<AdminFieldTypeProps> = props => {
// TODO add switch
return (
<div>

88
components/form/field.tsx Normal file
View File

@ -0,0 +1,88 @@
import {Form, message} from 'antd'
import {useForm} from 'antd/lib/form/Form'
import React from 'react'
import {FormDesignFragment, FormFieldFragment} from '../../graphql/fragment/form.fragment'
import {StyledButton} from '../styled/button'
import {StyledH1} from '../styled/h1'
import {StyledP} from '../styled/p'
import {fieldTypes} from './types'
import {TextType} from './types/text.type'
import {FieldTypeProps} from './types/type.props'
interface Props {
field: FormFieldFragment
design: FormDesignFragment
next: () => any
prev: () => any
}
export const Field: React.FC<Props> = ({field, design, children, next, prev, ...props}) => {
const [form] = useForm()
const FieldInput: React.FC<FieldTypeProps> = fieldTypes[field.type] || TextType
const finish = (data) => {
console.log('received field data', data)
next()
}
const error = () => {
message.error('Check inputs!')
}
return (
<Form
form={form}
onFinish={finish}
onFinishFailed={error}
{...props}
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<div style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
padding: 32,
justifyContent: 'flex-end',
}}>
<pre style={{
opacity: 0.3
}}>{JSON.stringify(field, null, 2)}</pre>
<StyledH1 design={design} type={'question'}>{field.title}</StyledH1>
{field.description && <StyledP design={design} type={'question'}>{field.description}</StyledP>}
<FieldInput
design={design}
field={field}
/>
</div>
<div style={{
padding: 32,
display: 'flex',
}}>
<StyledButton
background={design.colors.buttonColor}
color={design.colors.buttonTextColor}
highlight={design.colors.buttonActiveColor}
onClick={prev}
>{'Previous'}</StyledButton>
<div style={{flex: 1}} />
<StyledButton
background={design.colors.buttonColor}
color={design.colors.buttonTextColor}
highlight={design.colors.buttonActiveColor}
size={'large'}
onClick={form.submit}
>{'Next'}</StyledButton>
</div>
</Form>
)
}

70
components/form/page.tsx Normal file
View File

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

View File

@ -0,0 +1,22 @@
import {DatePicker, Form} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const DateType: React.FC<FieldTypeProps> = ({field}) => {
// TODO check min and max
return (
<div>
<Form.Item
label={'Default Date'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
labelCol={{ span: 6 }}
>
<DatePicker autoFocus />
</Form.Item>
</div>
)
}

View File

@ -0,0 +1,21 @@
import {Form, Input} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const DropdownType: React.FC<FieldTypeProps> = ({field}) => {
// TODO add dropdown options
return (
<div>
<Form.Item
label={'Default Value'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
labelCol={{ span: 6 }}
>
<Input />
</Form.Item>
</div>
)
}

View File

@ -0,0 +1,21 @@
import {Form, Input} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const EmailType: React.FC<FieldTypeProps> = ({field}) => {
return (
<div>
<Form.Item
label={'Default Email'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
{ type: 'email', message: 'Must be a valid email' }
]}
labelCol={{ span: 6 }}
>
<Input type={'email'} />
</Form.Item>
</div>
)
}

View File

@ -0,0 +1,20 @@
import {Form, Input} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const HiddenType: React.FC<FieldTypeProps> = ({field}) => {
return (
<div>
<Form.Item
label={'Default Value'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
labelCol={{ span: 6 }}
>
<Input />
</Form.Item>
</div>
)
}

View File

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

View File

@ -0,0 +1,21 @@
import {Form, Input} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const LinkType: React.FC<FieldTypeProps> = ({field}) => {
return (
<div>
<Form.Item
label={'Default Link'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
{ type: 'url', message: 'Must be a valid URL' }
]}
labelCol={{ span: 6 }}
>
<Input type={'url'} />
</Form.Item>
</div>
)
}

View File

@ -0,0 +1,21 @@
import {Form, InputNumber} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const NumberType: React.FC<FieldTypeProps> = ({field}) => {
return (
<div>
<Form.Item
label={'Default Number'}
name={[field.id, 'value']}
rules={[
{ type: 'number', message: 'Must be a valid URL' },
{ required: field.required, message: 'Please provide Information' },
]}
labelCol={{ span: 6 }}
>
<InputNumber />
</Form.Item>
</div>
)
}

View File

@ -0,0 +1,22 @@
import {Form, Input} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const RadioType: React.FC<FieldTypeProps> = ({field}) => {
// TODO Add radio support
return (
<div>
<Form.Item
label={'Default Value'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
labelCol={{ span: 6 }}
>
<Input />
</Form.Item>
</div>
)
}

View File

@ -0,0 +1,22 @@
import {Form, Input} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const RatingType: React.FC<FieldTypeProps> = ({field}) => {
// TODO add ratings
return (
<div>
<Form.Item
label={'Default Value'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
labelCol={{ span: 6 }}
>
<Input />
</Form.Item>
</div>
)
}

View File

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

View File

@ -0,0 +1,20 @@
import {Form, Input} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const TextareaType: React.FC<FieldTypeProps> = ({field}) => {
return (
<div>
<Form.Item
label={'Default Value'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
labelCol={{ span: 6 }}
>
<Input.TextArea autoSize />
</Form.Item>
</div>
)
}

View File

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

View File

@ -0,0 +1,21 @@
import {Form, Input} from 'antd'
import React from 'react'
import {FieldTypeProps} from './type.props'
export const YesNoType: React.FC<FieldTypeProps> = ({field}) => {
// TODO add switch
return (
<div>
<Form.Item
label={'Default Value'}
name={[field.id, 'value']}
rules={[
{ required: field.required, message: 'Please provide Information' },
]}
labelCol={{ span: 6 }}
>
<Input />
</Form.Item>
</div>
)
}

View File

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

View File

@ -0,0 +1,35 @@
/**
* @link https://css-tricks.com/snippets/javascript/lighten-darken-color/
*
* @author Chris Coyier
*/
function LightenDarkenColor(col, amt) {
let usePound = false;
if (col[0] == "#") {
col = col.slice(1);
usePound = true;
}
const num = parseInt(col, 16)
let r = (num >> 16) + amt;
if (r > 255) r = 255;
else if (r < 0) r = 0;
let b = ((num >> 8) & 0x00FF) + amt;
if (b > 255) b = 255;
else if (b < 0) b = 0;
let g = (num & 0x0000FF) + amt;
if (g > 255) g = 255;
else if (g < 0) g = 0;
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
}
export const lighten = (color: string, amount: number) => LightenDarkenColor(color, amount)
export const darken = (color: string, amount: number) => LightenDarkenColor(color, -amount)

18
components/styled/h1.tsx Normal file
View File

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

18
components/styled/h2.tsx Normal file
View File

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

View File

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

18
components/styled/p.tsx Normal file
View File

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

View File

View File

@ -0,0 +1,109 @@
import {gql} from 'apollo-boost'
export interface FormPageFragment {
show: boolean
title?: string
paragraph?: string
buttonText?: string
buttons: {
url?: string
action?: string
text?: string
bgColor?: string
activeColor?: string
color?: string
}[]
}
export interface FormFieldFragment {
id: string
title: string
type: string
description: string
required: boolean
value: string
}
export interface FormDesignFragment {
colors: {
backgroundColor: string
questionColor: string
answerColor: string
buttonColor: string
buttonActiveColor: string
buttonTextColor: string
}
font?: string
}
export interface FormFragment {
id?: string
title: string
created: string
language: string
showFooter: boolean
fields: FormFieldFragment[]
design: FormDesignFragment
startPage: FormPageFragment
endPage: FormPageFragment
}
export const FORM_FRAGMENT = gql`
fragment Form on Form {
id
title
language
showFooter
fields {
id
title
type
description
required
value
}
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
}
}
}
`

View File

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

View File

@ -13,6 +13,7 @@
"@ant-design/icons": "^4.1.0",
"@apollo/react-hooks": "^3.1.5",
"@lifeomic/axios-fetch": "^1.4.2",
"@types/swiper": "^5.3.1",
"antd": "^4.2.2",
"apollo-boost": "^0.4.9",
"axios": "^0.19.2",
@ -27,11 +28,14 @@
"react-color": "^2.18.1",
"react-dom": "16.13.1",
"react-icons": "^3.10.0",
"react-id-swiper": "^3.0.0",
"react-redux": "^7.2.0",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0",
"sass": "^1.26.5"
"sass": "^1.26.5",
"styled-components": "^5.1.1",
"swiper": "^5.4.1"
},
"devDependencies": {
"@types/node": "^14.0.1",

View File

@ -6,7 +6,7 @@ import {NextPage} from 'next'
import Link from 'next/link'
import React, {useState} from 'react'
import {DateTime} from '../../../components/date.time'
import {FormIsLive} from '../../../components/form/is.live'
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'

View File

@ -0,0 +1,3 @@
.page {
}

View File

@ -1,11 +1,102 @@
import {useQuery} from '@apollo/react-hooks'
import {NextPage} from 'next'
import React from 'react'
import React, {useState} from 'react'
import Swiper from 'react-id-swiper'
import {ReactIdSwiperProps} from 'react-id-swiper/lib/types'
import * as OriginalSwiper from 'swiper'
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 {useWindowSize} from '../../../components/use.window.size'
import {FORM_QUERY, FormQueryData, FormQueryVariables} from '../../../graphql/query/form.query'
interface Props {
id: string
}
const Index: NextPage<Props> = ({id}) => {
const windowSize = useWindowSize()
const [swiper, setSwiper] = useState<OriginalSwiper.default>(null)
const {loading, data, error} = useQuery<FormQueryData, FormQueryVariables>(FORM_QUERY, {
variables: {
id,
}
})
if (loading) {
return (
<LoadingPage message={'Building Form'} />
)
}
if (error) {
return (
<ErrorPage/>
)
}
const design = data.form.design
const goNext = () => {
if (!swiper) return
swiper.allowSlideNext = true
swiper.slideNext()
swiper.allowSlideNext = false
}
const goPrev = () => swiper && swiper.slidePrev()
const swiperConfig: ReactIdSwiperProps = {
getSwiper: setSwiper,
direction: 'vertical',
allowSlideNext: false,
allowSlidePrev: true,
updateOnWindowResize: true,
}
const Index: NextPage = () => {
return (
<ErrorPage />
<div style={{
background: design.colors.backgroundColor,
}}>
<Swiper {...swiperConfig}>
{[
<FormPage
key={'start'}
type={'start'}
page={data.form.startPage}
design={design}
next={goNext}
prev={goPrev}
/>,
...data.form.fields.map(field => (
<Field
key={field.id}
field={field}
design={design}
next={goNext}
prev={goPrev}
/>
)),
<FormPage
key={'end'}
type={'end'}
page={data.form.endPage}
design={design}
next={goNext}
prev={goPrev}
/>
]}
</Swiper>
</div>
)
}
Index.getInitialProps = async ({query}) => {
return {
id: query.id as string
}
}
export default Index

7080
yarn.lock Normal file

File diff suppressed because it is too large Load Diff