Compare commits
No commits in common. "52bb3790c7752afb02a257be36e827aa456a1c8a" and "7025c1a31bfb69d82a1e6b3d43c228078c276fbe" have entirely different histories.
52bb3790c7
...
7025c1a31b
13
README.md
13
README.md
@ -45,12 +45,10 @@
|
|||||||
### MVP1
|
### MVP1
|
||||||
|
|
||||||
**1. Landing**
|
**1. Landing**
|
||||||
|
|
||||||
- преимущества сервиса
|
- преимущества сервиса
|
||||||
- оставить заявку (редирект на Страницу оформления заказа)
|
- оставить заявку (редирект на Страницу оформления заказа)
|
||||||
|
|
||||||
**2. Страница для оформления заказа**
|
**2. Страница для оформления заказа**
|
||||||
|
|
||||||
- форма
|
- форма
|
||||||
- номер машины (mask input)
|
- номер машины (mask input)
|
||||||
- цвет машины
|
- цвет машины
|
||||||
@ -60,12 +58,10 @@
|
|||||||
- после заполнения редирект на страницу с деталями заказа
|
- после заполнения редирект на страницу с деталями заказа
|
||||||
|
|
||||||
**3. Страница с деталями заказа**
|
**3. Страница с деталями заказа**
|
||||||
|
|
||||||
- описание заказа
|
- описание заказа
|
||||||
- детали заказа (id, статус)
|
- детали заказа (id, статус)
|
||||||
|
|
||||||
**3. АРМ оператора**
|
**3. АРМ оператора**
|
||||||
|
|
||||||
- список заказов (RUD)
|
- список заказов (RUD)
|
||||||
- id заказа
|
- id заказа
|
||||||
- статус заказа (готово / не готово)
|
- статус заказа (готово / не готово)
|
||||||
@ -76,6 +72,7 @@
|
|||||||
- кнопка "Добавить"
|
- кнопка "Добавить"
|
||||||
- кнопка "Удалить"
|
- кнопка "Удалить"
|
||||||
|
|
||||||
|
|
||||||
### Built With
|
### Built With
|
||||||
|
|
||||||
[![React][React.js]][React-url]
|
[![React][React.js]][React-url]
|
||||||
@ -106,14 +103,6 @@
|
|||||||
|
|
||||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||||
|
|
||||||
## Instructions
|
|
||||||
### Stubs types generation
|
|
||||||
1. generate types with json-literal-typer (should be installed globally)
|
|
||||||
```sh
|
|
||||||
npx json-literal-typer -i <path to json> -o <path to output ts-file>
|
|
||||||
```
|
|
||||||
2. export default type from output file
|
|
||||||
|
|
||||||
<!-- PARTICIPANTS -->
|
<!-- PARTICIPANTS -->
|
||||||
|
|
||||||
## Participants
|
## Participants
|
||||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -6964,6 +6964,20 @@
|
|||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
@ -1,29 +1,52 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
MdEco,
|
||||||
|
MdMiscellaneousServices,
|
||||||
|
MdPlace,
|
||||||
|
MdHandshake,
|
||||||
|
} from 'react-icons/md';
|
||||||
import { Heading, HStack, List, Text, VStack } from '@chakra-ui/react';
|
import { Heading, HStack, List, Text, VStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { CtaButton, PageSection } from '../';
|
import { CtaButton, PageSection } from '../';
|
||||||
|
|
||||||
import { ListItem } from './ListItem';
|
import { ListItem } from './ListItem';
|
||||||
import { BenefitsSectionProps } from './types';
|
|
||||||
import { iconsMap } from './helper';
|
|
||||||
|
|
||||||
export const BenefitsSection: FC<BenefitsSectionProps> = ({
|
export const BenefitsSection: FC = () => {
|
||||||
data: { heading, description, list } = {},
|
const { t } = useTranslation('~', {
|
||||||
}) => {
|
keyPrefix: 'dry-wash.landing.benefits-section',
|
||||||
const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
|
});
|
||||||
|
|
||||||
|
const listData = [
|
||||||
|
{
|
||||||
|
Icon: MdEco,
|
||||||
|
children: t('list.0'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: MdMiscellaneousServices,
|
||||||
|
children: t('list.1'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: MdPlace,
|
||||||
|
children: t('list.2'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: MdHandshake,
|
||||||
|
children: t('list.3'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<VStack w='full' spacing={2}>
|
<VStack w='full' spacing={2}>
|
||||||
<Heading as='h2'>{t(heading)}</Heading>
|
<Heading as='h2'>{t('heading')}</Heading>
|
||||||
<Text>{t(description)}</Text>
|
<Text>
|
||||||
|
{t('description')}
|
||||||
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
<List display='flex' flexDirection='column' spacing={3}>
|
<List display='flex' flexDirection='column' spacing={3}>
|
||||||
{list.map((itemKey, i) => (
|
{listData.map((props, i) => (
|
||||||
<ListItem key={i} Icon={iconsMap[itemKey]}>
|
<ListItem key={i} {...props} />
|
||||||
{t(itemKey)}
|
|
||||||
</ListItem>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
<HStack w='full' justify='flex-end'>
|
<HStack w='full' justify='flex-end'>
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { IconType } from "react-icons";
|
|
||||||
import { MdEco, MdMiscellaneousServices, MdPlace, MdHandshake } from "react-icons/md";
|
|
||||||
|
|
||||||
import { ArrElement } from "../../../lib";
|
|
||||||
|
|
||||||
import { BenefitsList } from "./types";
|
|
||||||
|
|
||||||
export const iconsMap: Record<ArrElement<BenefitsList>, IconType> = {
|
|
||||||
"benefits-section.list.0": MdEco,
|
|
||||||
"benefits-section.list.1": MdMiscellaneousServices,
|
|
||||||
"benefits-section.list.2": MdPlace,
|
|
||||||
"benefits-section.list.3": MdHandshake,
|
|
||||||
};
|
|
@ -1,2 +1 @@
|
|||||||
export type { BenefitsSectionProps } from './types';
|
|
||||||
export { BenefitsSection } from './BenefitsSection';
|
export { BenefitsSection } from './BenefitsSection';
|
@ -1,14 +0,0 @@
|
|||||||
export type BenefitsList = [
|
|
||||||
'benefits-section.list.0',
|
|
||||||
'benefits-section.list.1',
|
|
||||||
'benefits-section.list.2',
|
|
||||||
'benefits-section.list.3',
|
|
||||||
];
|
|
||||||
|
|
||||||
export type BenefitsSectionProps = {
|
|
||||||
data: {
|
|
||||||
heading: 'benefits-section.heading';
|
|
||||||
description: 'benefits-section.description';
|
|
||||||
list: BenefitsList;
|
|
||||||
};
|
|
||||||
};
|
|
@ -6,7 +6,7 @@ import { ButtonProps, Button } from '@chakra-ui/react';
|
|||||||
import { URLs } from '../../../__data__/urls';
|
import { URLs } from '../../../__data__/urls';
|
||||||
|
|
||||||
export const CtaButton: FC<ButtonProps> = (props) => {
|
export const CtaButton: FC<ButtonProps> = (props) => {
|
||||||
const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@ -15,7 +15,7 @@ export const CtaButton: FC<ButtonProps> = (props) => {
|
|||||||
colorScheme='primary'
|
colorScheme='primary'
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{t('make-order-button')}
|
{t('~:dry-wash.landing.make-order-button')}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Box, Heading, Text, Center, VStack } from '@chakra-ui/react';
|
import { Box, Heading, Text, Center, VStack, BoxProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { DemoVideoPosterImg } from '../../../assets/images';
|
import { DemoVideoPosterImg } from '../../../assets/images';
|
||||||
import { CtaButton, SiteLogo, PageSection } from '../';
|
import { CtaButton, SiteLogo, PageSection } from '../';
|
||||||
|
|
||||||
import { HeroSectionProps } from './types';
|
type HeroSectionProps = Pick<BoxProps, 'flexShrink'>;
|
||||||
|
|
||||||
export const HeroSection: FC<HeroSectionProps> = ({
|
export const HeroSection: FC<HeroSectionProps> = ({ flexShrink }) => {
|
||||||
data: { headline, description, video } = {},
|
const { t } = useTranslation('~', {
|
||||||
flexShrink,
|
keyPrefix: 'dry-wash.landing.hero-section',
|
||||||
}) => {
|
});
|
||||||
const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexShrink={flexShrink} as='header' pos='relative' zIndex={0}>
|
<Box flexShrink={flexShrink} as='header' pos='relative' zIndex={0}>
|
||||||
<Box
|
<Box
|
||||||
as='video'
|
as='video'
|
||||||
src={`${__webpack_public_path__}/remote-assets/${video}`}
|
src={`${__webpack_public_path__}/remote-assets/demo.mp4`}
|
||||||
poster={DemoVideoPosterImg}
|
poster={DemoVideoPosterImg}
|
||||||
autoPlay
|
autoPlay
|
||||||
loop
|
loop
|
||||||
@ -48,14 +47,14 @@ export const HeroSection: FC<HeroSectionProps> = ({
|
|||||||
color='white'
|
color='white'
|
||||||
__css={{ textWrap: 'balance' }}
|
__css={{ textWrap: 'balance' }}
|
||||||
>
|
>
|
||||||
{t(headline)}
|
{t('headline')}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text
|
<Text
|
||||||
textAlign='center'
|
textAlign='center'
|
||||||
__css={{ textWrap: 'balance' }}
|
__css={{ textWrap: 'balance' }}
|
||||||
color='white'
|
color='white'
|
||||||
>
|
>
|
||||||
{t(description)}
|
{t('description')}
|
||||||
</Text>
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
<CtaButton size='lg' />
|
<CtaButton size='lg' />
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { BoxProps } from "@chakra-ui/react";
|
|
||||||
|
|
||||||
export type HeroSectionProps = {
|
|
||||||
data: {
|
|
||||||
headline: 'hero-section.headline';
|
|
||||||
description: 'hero-section.description';
|
|
||||||
video: string;
|
|
||||||
};
|
|
||||||
} & Pick<BoxProps, 'flexShrink'>;
|
|
@ -5,16 +5,15 @@ import { Heading, HStack } from '@chakra-ui/react';
|
|||||||
import { CtaButton, PageSection } from '../';
|
import { CtaButton, PageSection } from '../';
|
||||||
|
|
||||||
import { ReviewsSlider } from './ReviewsSlider';
|
import { ReviewsSlider } from './ReviewsSlider';
|
||||||
import { SocialProofSectionProps } from './types';
|
|
||||||
|
|
||||||
export const SocialProofSection: FC<SocialProofSectionProps> = ({
|
export const SocialProofSection: FC = () => {
|
||||||
data: { heading } = {},
|
const { t } = useTranslation('~', {
|
||||||
}) => {
|
keyPrefix: 'dry-wash.landing.social-proof-section',
|
||||||
const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Heading as='h2'>{t(heading)}</Heading>
|
<Heading as='h2'>{t('heading')}</Heading>
|
||||||
<ReviewsSlider />
|
<ReviewsSlider />
|
||||||
<HStack w='full' justify='flex-end'>
|
<HStack w='full' justify='flex-end'>
|
||||||
<CtaButton />
|
<CtaButton />
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export type { SocialProofSectionProps } from './types';
|
|
||||||
export { SocialProofSection } from './SocialProofSection';
|
export { SocialProofSection } from './SocialProofSection';
|
@ -1,5 +0,0 @@
|
|||||||
export type SocialProofSectionProps = {
|
|
||||||
data: {
|
|
||||||
heading: 'social-proof-section.heading';
|
|
||||||
};
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
export * from './types';
|
|
@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* @example type Output = ArrElement<['a', 'b', 'c']>;
|
|
||||||
* // "a" | "b" | "c"
|
|
||||||
*/
|
|
||||||
export type ArrElement<ArrType> = ArrType extends readonly (infer ElementType)[]
|
|
||||||
? ElementType
|
|
||||||
: never;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example type Output = Split<'a.b1' | 'a.b2', '.'>;
|
|
||||||
* // ["a", "b1"] | ["a", "b2"]
|
|
||||||
*/
|
|
||||||
type Split<S extends string, D extends string> =
|
|
||||||
S extends `${infer A}${D}${infer B}` ? [A, ...Split<B, D>] : [S];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example type Output = NestedObject<["a", "b1"] | ["a", "b2"]>;
|
|
||||||
* // { a: { b1: string; }; } | { a: { b2: string; }; }
|
|
||||||
*/
|
|
||||||
type NestedObject<T extends string[]> =
|
|
||||||
T extends [infer Head, ...infer Tail] ?
|
|
||||||
Head extends string ?
|
|
||||||
{ [key in Head]: NestedObject<Tail extends string[] ? Tail : []> } : never :
|
|
||||||
string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example type Output = UnionToIntersection<{ a: { b1: string; }; } | { a: { b2: string; }; }>;
|
|
||||||
* // { a: { b1: string; }; } & { a: { b2: string; }; }
|
|
||||||
*/
|
|
||||||
type UnionToIntersection<U> =
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example type Output = CreateTree<'a.b1' | 'a.b2', '.'>;
|
|
||||||
* // { a: { b1: string; }; } & { a: { b2: string; }; }
|
|
||||||
*/
|
|
||||||
export type CreateTree<T> =
|
|
||||||
UnionToIntersection<T extends infer U ?
|
|
||||||
U extends string ?
|
|
||||||
NestedObject<Split<U, '.'>> : never : never>;
|
|
23
src/models/i18next.d.ts
vendored
23
src/models/i18next.d.ts
vendored
@ -1,5 +1,24 @@
|
|||||||
import defaultLocale from '../../locales/ru.json';
|
import defaultLocale from '../../locales/ru.json';
|
||||||
import { CreateTree } from '../lib';
|
|
||||||
|
type Split<S extends string, D extends string> =
|
||||||
|
S extends `${infer A}${D}${infer B}` ? [A, ...Split<B, D>] : [S];
|
||||||
|
|
||||||
|
type NestedObject<T extends string[]> =
|
||||||
|
T extends [infer Head, ...infer Tail] ?
|
||||||
|
Head extends string ?
|
||||||
|
{ [key in Head]: NestedObject<Tail extends string[] ? Tail : []> } : never :
|
||||||
|
string;
|
||||||
|
|
||||||
|
// Основная утилита для обработки union type
|
||||||
|
type CreateTree<T> =
|
||||||
|
UnionToIntersection<T extends infer U ?
|
||||||
|
U extends string ?
|
||||||
|
NestedObject<Split<U, '.'>> : never : never>;
|
||||||
|
|
||||||
|
// Утилита для объединения типов
|
||||||
|
type UnionToIntersection<U> =
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
||||||
|
|
||||||
type LanguageResource = CreateTree<keyof typeof defaultLocale>;
|
type LanguageResource = CreateTree<keyof typeof defaultLocale>;
|
||||||
|
|
||||||
@ -7,6 +26,6 @@ declare module "i18next" {
|
|||||||
interface CustomTypeOptions {
|
interface CustomTypeOptions {
|
||||||
resources: {
|
resources: {
|
||||||
'~': LanguageResource
|
'~': LanguageResource
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1 +0,0 @@
|
|||||||
export * from './stubs';
|
|
@ -1 +0,0 @@
|
|||||||
export { default as LandingSuccessStub } from './success';
|
|
@ -1,27 +0,0 @@
|
|||||||
// Generated by json-literal-typer
|
|
||||||
// <-- BEGIN
|
|
||||||
interface HeroXsection {
|
|
||||||
description: "hero-section.description";
|
|
||||||
headline: "hero-section.headline";
|
|
||||||
video: "demo.mp4";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Sections {
|
|
||||||
description?: "benefits-section.description";
|
|
||||||
heading: "benefits-section.heading" | "social-proof-section.heading";
|
|
||||||
list?: ("benefits-section.list.0" | "benefits-section.list.1" | "benefits-section.list.2" | "benefits-section.list.3")[];
|
|
||||||
type: "benefits-section" | "social-proof-section";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Body {
|
|
||||||
"hero-section": HeroXsection;
|
|
||||||
sections: Sections[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Root {
|
|
||||||
body: Body;
|
|
||||||
success: true;
|
|
||||||
}
|
|
||||||
// END -->
|
|
||||||
|
|
||||||
export default Root;
|
|
@ -8,12 +8,6 @@ import {
|
|||||||
SocialProofSection,
|
SocialProofSection,
|
||||||
} from '../../components/landing';
|
} from '../../components/landing';
|
||||||
import { LandingThemeProvider } from '../../containers';
|
import { LandingThemeProvider } from '../../containers';
|
||||||
import { LandingSuccessStub } from '../../models/landing';
|
|
||||||
import landingSuccessStubJson from '../../../stubs/json/landing/success.json';
|
|
||||||
|
|
||||||
import { isBenefitsSectionData, isSocialProofSectionData } from './types';
|
|
||||||
|
|
||||||
const landingSuccessStub = landingSuccessStubJson as LandingSuccessStub;
|
|
||||||
|
|
||||||
const Page: FC = () => {
|
const Page: FC = () => {
|
||||||
return (
|
return (
|
||||||
@ -27,19 +21,10 @@ const Page: FC = () => {
|
|||||||
centerContent
|
centerContent
|
||||||
>
|
>
|
||||||
<VStack w='full' h='full' alignItems='stretch' flexGrow={1}>
|
<VStack w='full' h='full' alignItems='stretch' flexGrow={1}>
|
||||||
<HeroSection
|
<HeroSection flexShrink={0} />
|
||||||
data={landingSuccessStub['body']['hero-section']}
|
|
||||||
flexShrink={0}
|
|
||||||
/>
|
|
||||||
<VStack as='main' flexGrow={1}>
|
<VStack as='main' flexGrow={1}>
|
||||||
{landingSuccessStub.body.sections.map(({ type, ...data }, i) => {
|
<BenefitsSection />
|
||||||
if (isBenefitsSectionData(type, data)) {
|
<SocialProofSection />
|
||||||
return <BenefitsSection key={i} data={data} />;
|
|
||||||
}
|
|
||||||
if (isSocialProofSectionData(type, data)) {
|
|
||||||
return <SocialProofSection key={i} data={data} />;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</VStack>
|
</VStack>
|
||||||
<Footer />
|
<Footer />
|
||||||
</VStack>
|
</VStack>
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import LandingSuccess from "../../../stubs/json/landing/success.json";
|
|
||||||
import { BenefitsSectionProps, SocialProofSectionProps } from "../../components/landing";
|
|
||||||
import { ArrElement } from "../../lib";
|
|
||||||
|
|
||||||
type SectionsItemData = ArrElement<typeof LandingSuccess['body']['sections']>;
|
|
||||||
type SectionType = SectionsItemData['type'];
|
|
||||||
type SectionData = Omit<SectionsItemData, 'type'>;
|
|
||||||
|
|
||||||
export const isBenefitsSectionData = (type: SectionType, data: SectionData): data is BenefitsSectionProps['data'] => {
|
|
||||||
return type === 'benefits-section';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isSocialProofSectionData = (type: SectionType, data: SectionData): data is SocialProofSectionProps['data'] => {
|
|
||||||
return type === 'social-proof-section';
|
|
||||||
};
|
|
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"success": true,
|
|
||||||
"body": {
|
|
||||||
"hero-section": {
|
|
||||||
"headline": "hero-section.headline",
|
|
||||||
"description": "hero-section.description",
|
|
||||||
"video": "demo.mp4"
|
|
||||||
},
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"type": "benefits-section",
|
|
||||||
"heading": "benefits-section.heading",
|
|
||||||
"description": "benefits-section.description",
|
|
||||||
"list": [
|
|
||||||
"benefits-section.list.0",
|
|
||||||
"benefits-section.list.1",
|
|
||||||
"benefits-section.list.2",
|
|
||||||
"benefits-section.list.3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "social-proof-section",
|
|
||||||
"heading": "social-proof-section.heading"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user