Skip to content

Commit

Permalink
Merge pull request #57 from clean-commit/1.2.0
Browse files Browse the repository at this point in the history
Forms components added
  • Loading branch information
mrkaluzny authored Apr 18, 2024
2 parents 4cb0d22 + 7eb68b7 commit 49cd1ff
Show file tree
Hide file tree
Showing 14 changed files with 399 additions and 28 deletions.
37 changes: 20 additions & 17 deletions cms/cms.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import CMS from 'decap-cms-app';
import { Widget as UuidWidget } from 'netlify-cms-widget-id';
import { Widget as PermalinkWidget } from 'netlify-cms-widget-permalink';

import pages from './collections/pages';
import posts from './collections/posts';
import authors from './collections/authors';
import settings from './collections/settings';
import PagePreview from './previews/Page';
import CMS from 'decap-cms-app'
import { Widget as UuidWidget } from 'netlify-cms-widget-id'
import { Widget as PermalinkWidget } from 'netlify-cms-widget-permalink'
import authors from './collections/authors'
import forms from './collections/forms'
import pages from './collections/pages'
import posts from './collections/posts'
import settings from './collections/settings'
import FormPreview from './previews/FormPreview'
import PagePreview from './previews/Page'

const config = {
config: {
Expand All @@ -23,9 +24,14 @@ const config = {
},
media_folder: '/static/img',
public_folder: '/img',
collections: [pages, posts, authors, settings],
collections: [pages, posts, authors, forms, settings],
},
};
}


CMS.registerPreviewStyle('../commons.css')
CMS.registerPreviewTemplate('pages', PagePreview)
CMS.registerPreviewTemplate('forms', FormPreview)

const injectCustomStyle = () => {
const style = document.createElement('style')
Expand All @@ -39,10 +45,7 @@ const injectCustomStyle = () => {

injectCustomStyle()

CMS.registerPreviewStyle('../commons.css');
CMS.registerPreviewTemplate('pages', PagePreview);

CMS.registerWidget(UuidWidget);
CMS.registerWidget(PermalinkWidget);
CMS.registerWidget(UuidWidget)
CMS.registerWidget(PermalinkWidget)

CMS.init(config);
CMS.init(config)
37 changes: 37 additions & 0 deletions cms/previews/FormPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react'
import Form from '@/components/Form/Form'

export default class PagePreview extends React.Component {
render() {
const blocks = this.props.widgetsFor('rows').toJS()
let blocksUpdated = []
let hasBlocks = Array.isArray(blocks)
if (hasBlocks) {
blocksUpdated = blocks.map((block) => block.data)
}

return (
<div>
{hasBlocks ? (
<div className="mx-auto max-w-2xl py-24">
<Form
data={{
settings: {
resolver: 'Preview',
event_id: false,
success_msg: 'Thank you!',
},
rows: blocksUpdated,
}}
preview={true}
/>
</div>
) : (
<div class="flex items-center justify-center py-24 text-center">
<h1>Add first block to start creating your form</h1>
</div>
)}
</div>
)
}
}
9 changes: 0 additions & 9 deletions content/forms/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,5 @@ rows:
- fields:
- type: submit
label: Take the first step
- type: button
button:
variant: default
content: Let's go to google!!
url: 'https://google.com'
position: center
- position: bottom
fields:
- type: text
content: Yo yo yo
---
2 changes: 1 addition & 1 deletion jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"paths": {
"@/*": ["./src/*"],
"~/*": ["./*"],
"img": ["./static/img/*"]
"img/*": ["./static/img/*"]
}
},
"exclude": ["node_modules"]
Expand Down
1 change: 1 addition & 0 deletions src/blocks/Content.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import Container from '@/components/UI/Container'
import Section from '@/components/UI/Section'
import Text from '@/components/UI/Text'

export default function Content({ data }) {
Expand Down
147 changes: 147 additions & 0 deletions src/components/Form/Form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React, { useState } from 'react'
import { useForm } from 'react-hook-form'
import ButtonField from './partials/ButtonField'
import Checkbox from './partials/Checkbox'
import ContentSection from './partials/ContentSection'
import Input from './partials/Input'
import Submit from './partials/Submit'
import TextArea from './partials/TextArea'
import { handleGoal, cn } from '@/lib/helper'

const encode = (data) => {
return Object.keys(data)
.map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
.join('&')
}

export default function Form({ data, white }) {
const [isSend, setIsSend] = useState(false)
const [isSending, setIsSending] = useState(false)
const {
register,
handleSubmit,
setValue,
reset,
formState: { errors: fieldErrors },
} = useForm()

const onSubmit = async (formData) => {
setIsSending(true)
if (data.settings.resolver === 'Form') {
fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: encode({ 'form-name': data.title, ...formData }),
})
.then(() => {
setIsSend(true)
handleGoal(data.settings.event_id)
setTimeout(() => {
reset()
setIsSending(false)
}, 200)
})
.catch((error) => setIsSending(false))
}

if (data.settings.resolver === 'ConvertKit') {
const res = await fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify(formData),
})
const returned = JSON.parse(await res.json())
if (returned.success === true) {
setIsSend(true)
handleGoal(data.settings.event_id)
setTimeout(() => {
reset()
}, 200)
}
setIsSending(false)
}
}

return (
<div className="relative -mx-2 w-full overflow-hidden px-2 sm:w-auto">
<div
className={cn(
'absolute z-10 flex h-full w-full items-center justify-center transition-all',
{
'-translate-y-full opacity-0': !isSend,
},
)}
>
<div className="text-center text-2xl">
{data?.settings?.success_msg}
</div>
</div>
<form
className={cn('grid gap-6 transition-all', {
'-translate-y-full opacity-0': isSend,
})}
name={data.title}
onSubmit={handleSubmit(onSubmit)}
data-netlify={data.settings.resolver === 'Form'}
>
{data.rows &&
data.rows.map((row, i) => (
<div
key={i}
className={cn(
'flex flex-col justify-between gap-4 md:gap-6 lg:flex-row',
{
'items-center': row.position === 'center',
},
{
'items-end': row.position === 'bottom',
},
)}
>
{row.fields &&
row.fields.map((field, i) => {
switch (field.type) {
case 'input':
return (
<Input
data={field}
white={white}
key={i}
setValue={setValue}
register={register}
errors={fieldErrors}
/>
)
case 'textarea':
return (
<TextArea
data={field}
key={i}
register={register}
errors={fieldErrors}
/>
)
case 'checkbox':
return (
<Checkbox
data={field}
key={i}
register={register}
errors={fieldErrors}
/>
)
case 'submit':
return <Submit data={field} sending={isSending} key={i} />
case 'button':
return <ButtonField data={field} key={i} />
case 'text':
return <ContentSection data={field} key={i} />
default:
return <></>
}
})}
</div>
))}
</form>
</div>
)
}
13 changes: 13 additions & 0 deletions src/components/Form/FormWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import Form from './Form'
import { useForms } from '@/hooks/useForms'

export default function FormWrapper({ formId, white }) {
const forms = useForms()

const {
node: { frontmatter: form },
} = forms.find(({ node: item }) => item.frontmatter.id === formId)

return <Form data={form} white={white}></Form>
}
10 changes: 10 additions & 0 deletions src/components/Form/partials/ButtonField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import Button from '../../UI/Button'

export default function ButtonField({ data }) {
return (
<div>
<Button button={data?.button}>{data?.button?.content}</Button>
</div>
)
}
38 changes: 38 additions & 0 deletions src/components/Form/partials/Checkbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react'
import Label from '@/components/UI/Label'

export default function Checkbox({
data,
register,
disabled = false,
errors,
...props
}) {
const name = data?.name ? data?.name : 'test-check'
const error = errors[name]
return (
<div className="w-full ">
<Label htmlFor={name}>
<div className="flex cursor-pointer gap-3">
<div className="mt-0.5 flex items-start justify-center">
<input
type="checkbox"
{...props}
{...register(name)}
id={name}
disabled={disabled}
value={true}
aria-invalid={error ? 'true' : 'false'}
required={data?.required}
className="border-black-300 relative cursor-pointer rounded-sm bg-zinc-100 focus:ring-0"
/>
</div>
<div className="text-dark-500 text-sm font-normal">{data?.label}</div>
</div>
</Label>
<div className="block text-left text-sm text-red-600">
{error?.message}
</div>
</div>
)
}
10 changes: 10 additions & 0 deletions src/components/Form/partials/ContentSection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import Text from '@/components/UI/Text'

export default function ContentSection({ data }) {
return (
<div>
<Text className="text-sm">{data?.content}</Text>
</div>
)
}
50 changes: 50 additions & 0 deletions src/components/Form/partials/Input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react'
import classNames from 'clsx'
import { slugify, cn } from '@/lib/helper'

export default function Input({
data,
register,
disabled = false,
setValue,
errors,
white,
...props
}) {
const name = data?.label ? slugify(data?.label, '_') : 'preview'
const error = errors[name]
if (data?.value) {
setValue(name, data.value)
}

return (
<div
className={classNames('w-full', {
hidden: data?.input_type === 'hidden',
})}
>
<input
{...props}
{...register(name)}
id={name}
disabled={disabled}
autoComplete={data?.autocomplete || 'on'}
aria-invalid={error ? 'true' : 'false'}
required={data?.required}
type={data?.input_type}
placeholder={`${data?.label}${data?.required ? '*' : ''}`}
className={cn(
'block h-14 w-full rounded-lg border border-gray-600 px-5 transition-colors placeholder:text-gray-700 focus:border-blue-400 focus:ring-0 sm:h-16',
{
'w-full border-gray-400 bg-white sm:w-96': white,
'bg-gray-100': !white,
},
)}
></input>
<div className="block text-left text-sm text-red-600">
{error?.message}
</div>
{white}
</div>
)
}
Loading

0 comments on commit 49cd1ff

Please sign in to comment.