diff --git a/.storybook/stories/documentation/Blocks.mdx b/.storybook/stories/documentation/Blocks.mdx index c9c1c8875..b70ea7b28 100644 --- a/.storybook/stories/documentation/Blocks.mdx +++ b/.storybook/stories/documentation/Blocks.mdx @@ -54,3 +54,5 @@ _[Common field types](?id=documentation-types&viewMode=docs)_ ## [Table](?path=/story/blocks-table--docs&viewMode=docs) ## [Tabs](?path=/story/blocks-tabs--docs&viewMode=docs) + +## [Form](?path=/story/blocks-form--docs&viewMode=docs) diff --git a/src/blocks/Form/Form.scss b/src/blocks/Form/Form.scss new file mode 100644 index 000000000..425cd535a --- /dev/null +++ b/src/blocks/Form/Form.scss @@ -0,0 +1,228 @@ +@import '../../../styles/mixins.scss'; +@import '../../../styles/variables.scss'; + +$block: '.#{$ns}form-block'; + +$textPadding: 10px; +$maxLargeWidth: 609px; +$yandexFormDesktopMinWidth: 475px; +$largeBorderRadius: 32px; + +#{$block} { + $root: &; + + border-radius: $largeBorderRadius; + position: relative; + + &__title { + margin: 0 0 $indentSM $textPadding; + + &_mobile { + margin-left: 4px; + } + } + + &__full-form { + background-color: var(--g-color-base-background); + padding: $indentL $indentXL $indentL calc(#{$indentXL} - #{$textPadding}); + border-radius: $borderRadius; + } + + &__media { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + border-radius: $largeBorderRadius; + } + + &__image { + height: 100%; + width: 100%; + object-fit: cover; + } + + &__row { + &_direction { + &_form-content { + flex-direction: row-reverse; + } + + &_center { + padding-top: $indentXL; + padding-bottom: $indentL; + flex-direction: column; + + #{$root}__content-wrapper { + margin-bottom: $indentM; + } + } + } + } + + &:not(#{$root}_with-background) { + #{$root}__full-form { + box-shadow: 0 4px 24px var(--pc-color-sfx-shadow), 0 2px 8px var(--pc-color-sfx-shadow); + } + #{$root}__row { + &_direction { + &_form-content { + #{$root}__content-wrapper { + padding: $indentL 0 $indentXL $indentXL; + } + } + &_content-form { + #{$root}__content-wrapper { + padding: $indentL $indentXL $indentXL 0; + } + } + } + } + } + + &_with-background { + #{$root}__row { + &_direction { + &_form-content { + #{$root}__form-wrapper { + padding: $indentXS 0 $indentSM $indentXS; + } + } + &_content-form { + #{$root}__form-wrapper { + padding: $indentXS $indentXS $indentSM 0; + } + } + &_form-content, + &_content-form { + #{$root}__content-wrapper { + padding: $indentXL; + } + } + } + } + } + + @media (min-width: map-get($gridBreakpoints, 'lg')) { + &_form-type_yandex { + #{$root}__row { + &_direction { + &_form-content, + &_content-form { + #{$root}__content-col { + flex: 1 0 0; + } + } + + &_form-content, + &_content-form, + &_center { + #{$root}__form { + min-width: $yandexFormDesktopMinWidth; + } + #{$root}__form-col { + max-width: initial; + width: fit-content; + } + } + } + } + } + } + + @media (max-width: map-get($gridBreakpoints, 'lg')) and (min-width: map-get($gridBreakpoints, 'md')) { + &__row { + flex-direction: column; + } + + &_with-background, + &:not(#{$root}_with-background) { + #{$root}__row { + #{$root}__form-wrapper, + #{$root}__content-wrapper { + max-width: $maxLargeWidth; + } + + #{$root}__center, + #{$root}__form-wrapper, + #{$root}__content-wrapper { + margin: 0 auto; + padding-right: 0; + padding-left: 0; + } + + #{$root}__form-wrapper { + padding-top: 0; + } + + #{$root}__content-wrapper { + text-align: center; + padding-bottom: $indentM; + } + } + } + + &:not(#{$root}_with-background) &__row { + #{$root}__content-wrapper { + padding: 0 0 $indentM 0; + } + } + } + + @media (max-width: map-get($gridBreakpoints, 'md')) { + &__full-form { + padding: $indentM; + } + + &_with-background, + &:not(#{$root}_with-background) { + #{$root}__row { + padding: 0; + + #{$root}__form-wrapper, + #{$root}__content-wrapper { + padding: 0; + } + + #{$root}__content-wrapper { + padding-bottom: $indentM; + margin-bottom: 0; + } + } + } + + &_with-background { + padding: 0 $indentXXXS; + + #{$root}__row { + padding-top: $indentL; + + &_padding-bottom { + &_m { + padding-bottom: $indentM; + } + &_l { + padding-bottom: $indentL; + } + } + + &_direction { + &_form-content, + &_content-form, + &_center { + #{$root}__content-wrapper { + padding-right: $indentXS; + padding-left: $indentXS; + } + } + + &_form-content, + &_content-form { + padding-top: $indentM; + } + } + } + } + } +} diff --git a/src/blocks/Form/Form.tsx b/src/blocks/Form/Form.tsx new file mode 100644 index 000000000..ef1de0e61 --- /dev/null +++ b/src/blocks/Form/Form.tsx @@ -0,0 +1,117 @@ +import React, {useCallback, useContext, useState} from 'react'; + +import {BackgroundImage, Title} from '../../components'; +import {MobileContext} from '../../context/mobileContext'; +import {Col, Grid, GridAlignItems, GridColumnSize, Row} from '../../grid'; +import type {FormBlockProps} from '../../models'; +import { + FormBlockDataTypes, + FormBlockDirection, + isHubspotDataForm, + isYandexDataForm, +} from '../../models'; +import {Content} from '../../sub-blocks'; +import {block} from '../../utils'; + +import InnerForm from './InnerForm/InnerForm'; + +import './Form.scss'; + +const b = block('form-block'); + +const colSizes = {[GridColumnSize.Lg]: 6, [GridColumnSize.All]: 12}; + +const FormBlock: React.FC = (props) => { + const {formData, title, textContent, direction = FormBlockDirection.Center, background} = props; + const [contentLoaded, setContentLoaded] = useState(false); + const isMobile = useContext(MobileContext); + + const hasImage = background && (background.src || background.desktop); + const paddingBottom = background && background.style?.backgroundColor && !hasImage ? 'l' : 'm'; // bigger padding for case with background color and no image + const onContentLoad = useCallback(() => { + setContentLoaded(true); + }, []); + + if (!formData) { + return null; + } + + let formType; + + if (isYandexDataForm(formData)) { + formType = FormBlockDataTypes.YANDEX; + } else if (isHubspotDataForm(formData)) { + formType = FormBlockDataTypes.HUBSPOT; + } + + return ( +
+ {background && ( + + )} + + + + {textContent && ( +
+ +
+ )} + + +
+
+ {title && ( + + )} + <InnerForm + className={b('form')} + formData={formData} + onContentLoad={onContentLoad} + /> + </div> + </div> + </Col> + </Row> + </Grid> + </div> + ); +}; + +export default FormBlock; diff --git a/src/blocks/Form/InnerForm/InnerForm.tsx b/src/blocks/Form/InnerForm/InnerForm.tsx new file mode 100644 index 000000000..42fdca190 --- /dev/null +++ b/src/blocks/Form/InnerForm/InnerForm.tsx @@ -0,0 +1,44 @@ +import React, {useEffect} from 'react'; + +import {YandexForm} from '../../../components'; +import {FormBlockData, isHubspotDataForm, isYandexDataForm} from '../../../models'; +import {HubspotForm} from '../../../sub-blocks'; + +interface InnerFormProps { + formData: FormBlockData; + onContentLoad: () => void; + className?: string; +} + +const InnerForm: React.FC<InnerFormProps> = (props) => { + const {formData, onContentLoad, className} = props; + + useEffect(() => { + if (isHubspotDataForm(formData)) { + onContentLoad(); + } + }, [onContentLoad, formData]); + + if (isYandexDataForm(formData)) { + const {onLoad, ...rest} = formData.yandex; + return ( + <div className={className}> + <YandexForm + {...rest} + onLoad={() => { + onContentLoad(); + onLoad?.(); + }} + /> + </div> + ); + } + + if (isHubspotDataForm(formData)) { + return <HubspotForm createDOMElement={true} {...formData.hubspot} />; + } + + return null; +}; + +export default InnerForm; diff --git a/src/blocks/Form/__stories__/Form.mdx b/src/blocks/Form/__stories__/Form.mdx new file mode 100644 index 000000000..3b177fe89 --- /dev/null +++ b/src/blocks/Form/__stories__/Form.mdx @@ -0,0 +1,24 @@ +import {Meta} from '@storybook/blocks'; + +import {StoryTemplate} from '../../../demo/StoryTemplate.mdx'; +import * as FormBlockStories from './Form.stories.tsx'; + +<Meta of={FormBlockStories} /> +<StoryTemplate> +## Parameters + +`type: "form-block"` + +`title?: string` - Title. + +`textContent?` - See the [Content](?path=/story/components-content--default&viewMode=docs) component. + +`formData: {yandex?: Object, hubspot?: Object}` - See [Yandex](?path=/story/components-yandexform--docs&viewMode=docs) / [Hubspot](?path=/story/components-hubspotform--docs&viewMode=docs) form data. + +`direction?: 'form-content' | 'content-form' | 'center'` - Direction. + +`image?: string | {src: string; alt?: string;}` — Image. + +`backgroundColor?: string` — Bg color. + +</StoryTemplate> diff --git a/src/blocks/Form/__stories__/Form.stories.tsx b/src/blocks/Form/__stories__/Form.stories.tsx new file mode 100644 index 000000000..9d12caaab --- /dev/null +++ b/src/blocks/Form/__stories__/Form.stories.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +import {Meta, StoryFn} from '@storybook/react'; + +import {PageConstructor} from '../../../containers/PageConstructor'; +import {FormBlockDirection, FormBlockModel} from '../../../models'; +import FormBlock from '../Form'; + +import data from './data.json'; + +export default { + title: 'Blocks/Form', + component: FormBlock, + args: data.default, + argTypes: { + type: {control: false}, + direction: {options: FormBlockDirection, control: {type: 'select'}}, + }, +} as Meta; + +const DefaultTemplate: StoryFn<FormBlockModel> = (args) => ( + <PageConstructor + content={{ + blocks: [ + { + ...args, + title: 'Yandex form', + formData: {yandex: data.formData.yandex}, //yandex form + }, + { + ...args, + title: 'Hubspot form', + formData: {hubspot: data.formData.hubspot}, //hubspot form + }, + ], + }} + /> +); + +export const Default = DefaultTemplate.bind({}); +export const ContentDirection = DefaultTemplate.bind({}); +export const WithBackgroundColor = DefaultTemplate.bind({}); +export const WithBackgroundImage = DefaultTemplate.bind({}); +export const DarkTheme = DefaultTemplate.bind({}); + +ContentDirection.args = {...data.сontentDirection.content} as FormBlockModel; + +WithBackgroundColor.args = {...data.withBackground.content} as FormBlockModel; + +WithBackgroundImage.args = {...data.withBackgroundImage.content} as FormBlockModel; + +DarkTheme.args = {...data.darkTheme.content} as FormBlockModel; diff --git a/src/blocks/Form/__stories__/data.json b/src/blocks/Form/__stories__/data.json new file mode 100644 index 000000000..419c0f94e --- /dev/null +++ b/src/blocks/Form/__stories__/data.json @@ -0,0 +1,59 @@ +{ + "default": { + "type": "form-block", + "textContent": { + "title": "Lorem ipsum dolor sit amet", + "text": "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + } + }, + "сontentDirection": { + "content": { + "direction": "content-form" + } + }, + "withBackground": { + "content": { + "background": { + "style": { + "backgroundColor": "#EFF2F8" + } + } + } + }, + "withBackgroundImage": { + "content": { + "background": { + "src": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/content-bg-img_light.png", + "disableCompress": true + } + } + }, + "darkTheme": { + "content": { + "textContent": { + "theme": "dark", + "title": "Black form background color", + "text": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit:</p> <ul> <li> <p>Lorem ipsum</p> </li> <li> <p>Lorem ipsum</p> </li> <li> <p>Lorem ipsum</p> </li> </ul>" + }, + "direction": "form-content", + "background": { + "style": { + "backgroundColor": "#262626" + } + } + } + }, + "formData": { + "yandex": { + "theme": "default", + "id": "61a4e639d4d24e0dbba36f5c", + "metrikaGoals": ["events_form_submit"], + "customFormSection": "cloud" + }, + "hubspot": { + "region": "eu1", + "portalId": "25764979", + "formId": "a3eb06a6-e8ce-45d4-81bd-7fadb7dab313" + } + } +} diff --git a/src/blocks/Form/schema.ts b/src/blocks/Form/schema.ts new file mode 100644 index 000000000..140bb9849 --- /dev/null +++ b/src/blocks/Form/schema.ts @@ -0,0 +1,51 @@ +import omit from 'lodash/omit'; + +import {ImageProps} from '../../components/Image/schema'; +import {YandexFormProps} from '../../components/YandexForm/schema'; +import {BlockBaseProps} from '../../schema/validators/common'; +import {ContentBase} from '../../sub-blocks/Content/schema'; +import {HubspotFormProps} from '../../sub-blocks/HubspotForm/schema'; + +const FormBlockContentProps = omit(ContentBase, ['size', 'centered', 'colSizes']); + +export const FormBlock = { + 'form-block': { + additionalProperties: false, + required: ['formData'], + properties: { + ...BlockBaseProps, + title: { + type: 'string', + }, + formData: { + oneOf: [ + { + type: 'object', + optionName: 'yandex', + properties: { + yandex: YandexFormProps, + }, + }, + { + type: 'object', + optionName: 'hubspot', + properties: { + hubspot: HubspotFormProps, + }, + }, + ], + }, + textContent: { + additionalProperties: false, + properties: FormBlockContentProps, + }, + direction: { + enum: ['content-form', 'form-content', 'center'], + }, + image: ImageProps, + backgroundColor: { + type: 'string', + }, + }, + }, +}; diff --git a/src/blocks/index.ts b/src/blocks/index.ts index 78112c350..6ce472095 100644 --- a/src/blocks/index.ts +++ b/src/blocks/index.ts @@ -16,3 +16,4 @@ export {default as CardLayoutBlock} from './CardLayout/CardLayout'; export {default as ContentLayoutBlock} from './ContentLayout/ContentLayout'; export {default as ShareBlock} from './Share/Share'; export {default as FilterBlock} from './FilterBlock/FilterBlock'; +export {default as FormBlock} from './Form/Form'; diff --git a/src/components/YandexForm/YandexForm.tsx b/src/components/YandexForm/YandexForm.tsx index ac13f0251..08e0811dd 100644 --- a/src/components/YandexForm/YandexForm.tsx +++ b/src/components/YandexForm/YandexForm.tsx @@ -4,32 +4,17 @@ import {LocaleContext} from '../../context/localeContext'; import {MobileContext} from '../../context/mobileContext'; import {useAnalytics} from '../../hooks'; import {useMetrika} from '../../hooks/useMetrika'; -import {PixelEvent} from '../../models'; -import {AnalyticsEventsBase, DefaultEventNames} from '../../models/common'; +import {YandexFormProps} from '../../models'; +import {DefaultEventNames} from '../../models/common'; import {block} from '../../utils'; import {HEADER_HEIGHT} from '../constants'; export const YANDEX_FORM_ORIGIN = 'https://forms.yandex.ru'; +export const YANDEX_FORM_SECTION = 'surveys'; const CONTAINER_ID = 'pc-yandex-form-container'; const b = block('yandex-form'); -export interface YandexFormProps extends AnalyticsEventsBase { - id: number | string; - containerId?: string; - theme?: string; - className?: string; - headerHeight?: number; - customFormOrigin?: string; - params?: {[key: string]: string}; - - onSubmit?: () => void; - onLoad?: () => void; - - metrikaGoals?: string | string[]; - pixelEvents?: string | string[] | PixelEvent | PixelEvent[]; -} - const YandexForm = (props: YandexFormProps) => { const { onLoad, @@ -44,10 +29,12 @@ const YandexForm = (props: YandexFormProps) => { pixelEvents, analyticsEvents, customFormOrigin, + customFormSection, } = props; const formContainerRef = useRef<HTMLDivElement>(null); const iframeRef = useRef<HTMLIFrameElement>(); const yaFormOrigin = customFormOrigin || YANDEX_FORM_ORIGIN; + const yaFormSection = customFormSection || YANDEX_FORM_SECTION; const handleMetrika = useMetrika(); const handleAnalytics = useAnalytics(DefaultEventNames.YandexFormSubmit); @@ -78,7 +65,7 @@ const YandexForm = (props: YandexFormProps) => { }); } - const src = `${yaFormOrigin}/surveys/${id}/?${queryParams}`; + const src = `${yaFormOrigin}/${yaFormSection}/${id}/?${queryParams}`; if (iframeRef.current) { iframeRef.current.src = src; @@ -94,7 +81,7 @@ const YandexForm = (props: YandexFormProps) => { container.appendChild(iframeRef.current); } }, - [locale.lang, theme, isMobile, yaFormOrigin, id, containerId, params], + [locale.lang, theme, isMobile, yaFormOrigin, yaFormSection, id, containerId, params], ); const handleSubmit = useCallback(() => { diff --git a/src/components/YandexForm/__stories__/YandexForm.stories.tsx b/src/components/YandexForm/__stories__/YandexForm.stories.tsx index d43fdf69e..20f37e473 100644 --- a/src/components/YandexForm/__stories__/YandexForm.stories.tsx +++ b/src/components/YandexForm/__stories__/YandexForm.stories.tsx @@ -2,7 +2,8 @@ import React from 'react'; import {Meta, StoryFn} from '@storybook/react'; -import YandexForm, {YandexFormProps} from '../YandexForm'; +import {YandexFormProps} from '../../../models'; +import YandexForm from '../YandexForm'; import data from './data.json'; diff --git a/src/components/YandexForm/__stories__/data.json b/src/components/YandexForm/__stories__/data.json index 4c0df35f8..b1b896652 100644 --- a/src/components/YandexForm/__stories__/data.json +++ b/src/components/YandexForm/__stories__/data.json @@ -1,7 +1,8 @@ { "default": { "content": { - "id": "10034371.a94dac9d3bd19f65c9616db0297348ad8d063db0" + "id": "61a4e639d4d24e0dbba36f5c", + "customFormSection": "cloud" } } } diff --git a/src/components/YandexForm/schema.ts b/src/components/YandexForm/schema.ts new file mode 100644 index 000000000..33c419480 --- /dev/null +++ b/src/components/YandexForm/schema.ts @@ -0,0 +1,15 @@ +import {BaseProps} from '../../schema/validators/common'; + +export const YandexFormProps = { + type: 'object', + required: ['id'], + properties: { + ...BaseProps, + id: { + type: 'string', + }, + containerId: { + type: 'string', + }, + }, +}; diff --git a/src/constructor-items.ts b/src/constructor-items.ts index afa5c794e..95d4dfab7 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -5,6 +5,7 @@ import { ContentLayoutBlock, ExtendedFeaturesBlock, FilterBlock, + FormBlock, HeaderBlock, HeaderSliderBlock, IconsBlock, @@ -57,6 +58,7 @@ export const blockMap = { [BlockType.ShareBlock]: ShareBlock, [BlockType.MapBlock]: MapBlock, [BlockType.FilterBlock]: FilterBlock, + [BlockType.FormBlock]: FormBlock, }; export const subBlockMap = { diff --git a/src/editor/data/templates/form-block.json b/src/editor/data/templates/form-block.json new file mode 100644 index 000000000..c2de6da80 --- /dev/null +++ b/src/editor/data/templates/form-block.json @@ -0,0 +1,20 @@ +{ + "template": { + "type": "form-block", + "formData": { + "yandex": { + "hash": "hashString", + "title": "" + } + }, + "direction": "center", + "textContent": { + "title": "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + "text": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + }, + "image": { + "src": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/img-mini_4-12_light.png" + }, + "backgroundColor": "#262626" + } +} diff --git a/src/models/constructor-items/blocks.ts b/src/models/constructor-items/blocks.ts index 7e04338eb..18442bb2c 100644 --- a/src/models/constructor-items/blocks.ts +++ b/src/models/constructor-items/blocks.ts @@ -33,8 +33,9 @@ import { ThemedMediaVideoProps, TitleItemBaseProps, TitleItemProps, + YandexFormProps, } from './common'; -import {BannerCardProps, SubBlock, SubBlockModels} from './sub-blocks'; +import {BannerCardProps, HubspotFormProps, SubBlock, SubBlockModels} from './sub-blocks'; export enum BlockType { PromoFeaturesBlock = 'promo-features-block', @@ -55,6 +56,7 @@ export enum BlockType { ShareBlock = 'share-block', MapBlock = 'map-block', FilterBlock = 'filter-block', + FormBlock = 'form-block', } export const BlockTypes = Object.values(BlockType); @@ -378,6 +380,35 @@ export interface ShareBlockProps { title?: string; } +export enum FormBlockDataTypes { + YANDEX = 'yandex', + HUBSPOT = 'hubspot', +} + +export enum FormBlockDirection { + FormContent = 'form-content', + ContentForm = 'content-form', + Center = 'center', +} + +export interface FormBlockYandexData { + yandex: YandexFormProps; +} + +export interface FormBlockHubspotData { + hubspot: HubspotFormProps; +} + +export type FormBlockData = FormBlockYandexData | FormBlockHubspotData; + +export interface FormBlockProps { + formData: FormBlockData; + title?: string; + textContent?: Omit<ContentBlockProps, 'centered' | 'colSizes' | 'size'>; + direction?: FormBlockDirection; + background?: BackgroundImageProps; +} + //block models export type HeaderBlockModel = { type: BlockType.HeaderBlock; @@ -451,6 +482,10 @@ export type ShareBLockModel = { type: BlockType.ShareBlock; } & ShareBlockProps; +export type FormBlockModel = { + type: BlockType.FormBlock; +} & FormBlockProps; + type BlockModels = | SliderBlockModel | ExtendedFeaturesBlockModel @@ -469,6 +504,7 @@ type BlockModels = | CardLayoutBlockModel | ContentLayoutBlockModel | ShareBLockModel - | FilterBlockModel; + | FilterBlockModel + | FormBlockModel; export type Block = BlockModels & BlockBaseProps; diff --git a/src/models/constructor-items/common.ts b/src/models/constructor-items/common.ts index b35814786..634a08f76 100644 --- a/src/models/constructor-items/common.ts +++ b/src/models/constructor-items/common.ts @@ -3,7 +3,7 @@ import React, {CSSProperties, HTMLProps, ReactNode} from 'react'; import {ButtonView, ButtonProps as UikitButtonProps} from '@gravity-ui/uikit'; import {ThemeSupporting} from '../../utils'; -import {AnalyticsEventsBase, ClassNameProps, PixelEventType, QAProps} from '../common'; +import {AnalyticsEventsBase, ClassNameProps, PixelEvent, PixelEventType, QAProps} from '../common'; // enums export enum AuthorType { @@ -444,3 +444,20 @@ export interface TitleProps { title?: TitleItemProps | string; subtitle?: string; } + +export interface YandexFormProps extends AnalyticsEventsBase { + id: number | string; + containerId?: string; + theme?: string; + className?: string; + headerHeight?: number; + customFormOrigin?: string; + customFormSection?: string; + params?: {[key: string]: string}; + + onSubmit?: () => void; + onLoad?: () => void; + + metrikaGoals?: string | string[]; + pixelEvents?: string | string[] | PixelEvent | PixelEvent[]; +} diff --git a/src/models/guards.ts b/src/models/guards.ts index 8878e3119..98a84b8f7 100644 --- a/src/models/guards.ts +++ b/src/models/guards.ts @@ -1,4 +1,11 @@ -import {Block, BlockTypes, ConstructorItem} from './'; +import { + Block, + BlockTypes, + ConstructorItem, + FormBlockData, + FormBlockHubspotData, + FormBlockYandexData, +} from './'; import {MetrikaGoal, NewMetrikaGoal} from './index'; export function isBlock(block: ConstructorItem): block is Block { @@ -8,3 +15,11 @@ export function isBlock(block: ConstructorItem): block is Block { export function isNewMetrikaFormat(metrika: MetrikaGoal): metrika is NewMetrikaGoal[] { return Boolean(Array.isArray(metrika) && metrika.length && typeof metrika[0] === 'object'); } + +export function isYandexDataForm(data: FormBlockData): data is FormBlockYandexData { + return Boolean((data as FormBlockYandexData).yandex); +} + +export function isHubspotDataForm(data: FormBlockData): data is FormBlockHubspotData { + return Boolean((data as FormBlockHubspotData).hubspot); +} diff --git a/src/schema/constants.ts b/src/schema/constants.ts index 2ccfe6467..ce95ccbd3 100644 --- a/src/schema/constants.ts +++ b/src/schema/constants.ts @@ -8,6 +8,7 @@ import { ContentLayoutBlock, ExtendedFeaturesBlock, FilterBlock, + FormBlock, HeaderBlock, HeaderSliderBlock, IconsBlock, @@ -50,6 +51,7 @@ export const blockSchemas: Record<BlockType, object> = { ...ContentLayoutBlock, ...ShareBlock, ...FilterBlock, + ...FormBlock, }; export const cardSchemas = { @@ -86,6 +88,7 @@ export const constructorBlockSchemaNames = [ 'content-layout-block', 'share-block', 'filter-block', + 'form-block', ]; export const constructorCardSchemaNames = [ diff --git a/src/schema/validators/blocks.ts b/src/schema/validators/blocks.ts index 8aa30da91..53e949605 100644 --- a/src/schema/validators/blocks.ts +++ b/src/schema/validators/blocks.ts @@ -16,3 +16,4 @@ export * from '../../blocks/CardLayout/schema'; export * from '../../blocks/ContentLayout/schema'; export * from '../../blocks/Share/schema'; export * from '../../blocks/FilterBlock/schema'; +export * from '../../blocks/Form/schema'; diff --git a/src/sub-blocks/HubspotForm/schema.ts b/src/sub-blocks/HubspotForm/schema.ts new file mode 100644 index 000000000..9293d7c6e --- /dev/null +++ b/src/sub-blocks/HubspotForm/schema.ts @@ -0,0 +1,21 @@ +import {BaseProps} from '../../schema/validators/common'; + +export const HubspotFormProps = { + type: 'object', + required: ['portalId', 'formId'], + properties: { + ...BaseProps, + region: { + type: 'string', + }, + portalId: { + type: 'string', + }, + formId: { + type: 'string', + }, + formInstanceId: { + type: 'string', + }, + }, +};