diff --git a/README.md b/README.md
index 88785ca..7ea0dd1 100644
--- a/README.md
+++ b/README.md
@@ -45,10 +45,12 @@
### MVP1
**1. Landing**
+
- преимущества сервиса
- оставить заявку (редирект на Страницу оформления заказа)
**2. Страница для оформления заказа**
+
- форма
- номер машины (mask input)
- цвет машины
@@ -58,10 +60,12 @@
- после заполнения редирект на страницу с деталями заказа
**3. Страница с деталями заказа**
+
- описание заказа
- детали заказа (id, статус)
**3. АРМ оператора**
+
- список заказов (RUD)
- id заказа
- статус заказа (готово / не готово)
@@ -72,7 +76,6 @@
- кнопка "Добавить"
- кнопка "Удалить"
-
### Built With
[![React][React.js]][React-url]
@@ -103,6 +106,14 @@
(back to top)
+## Instructions
+### Stubs types generation
+1. generate types with json-literal-typer (should be installed globally)
+ ```sh
+ npx json-literal-typer -i -o
+ ```
+2. export default type from output file
+
## Participants
diff --git a/package-lock.json b/package-lock.json
index b24108c..406d991 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6964,20 +6964,6 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"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": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
diff --git a/src/components/landing/BenefitsSection/BenefitsSection.tsx b/src/components/landing/BenefitsSection/BenefitsSection.tsx
index dc74dbf..d03b322 100644
--- a/src/components/landing/BenefitsSection/BenefitsSection.tsx
+++ b/src/components/landing/BenefitsSection/BenefitsSection.tsx
@@ -1,52 +1,29 @@
import React, { FC } from 'react';
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 { CtaButton, PageSection } from '../';
import { ListItem } from './ListItem';
+import { BenefitsSectionProps } from './types';
+import { iconsMap } from './helper';
-export const BenefitsSection: FC = () => {
- const { t } = useTranslation('~', {
- keyPrefix: 'dry-wash.landing.benefits-section',
- });
-
- 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'),
- },
- ];
+export const BenefitsSection: FC = ({
+ data: { heading, description, list } = {},
+}) => {
+ const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
return (
- {t('heading')}
-
- {t('description')}
-
+ {t(heading)}
+ {t(description)}
- {listData.map((props, i) => (
-
+ {list.map((itemKey, i) => (
+
+ {t(itemKey)}
+
))}
diff --git a/src/components/landing/BenefitsSection/helper.ts b/src/components/landing/BenefitsSection/helper.ts
new file mode 100644
index 0000000..80f0784
--- /dev/null
+++ b/src/components/landing/BenefitsSection/helper.ts
@@ -0,0 +1,13 @@
+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, IconType> = {
+ "benefits-section.list.0": MdEco,
+ "benefits-section.list.1": MdMiscellaneousServices,
+ "benefits-section.list.2": MdPlace,
+ "benefits-section.list.3": MdHandshake,
+};
\ No newline at end of file
diff --git a/src/components/landing/BenefitsSection/index.ts b/src/components/landing/BenefitsSection/index.ts
index d10431b..f5cfc6d 100644
--- a/src/components/landing/BenefitsSection/index.ts
+++ b/src/components/landing/BenefitsSection/index.ts
@@ -1 +1,2 @@
+export type { BenefitsSectionProps } from './types';
export { BenefitsSection } from './BenefitsSection';
\ No newline at end of file
diff --git a/src/components/landing/BenefitsSection/types.ts b/src/components/landing/BenefitsSection/types.ts
new file mode 100644
index 0000000..4099a0d
--- /dev/null
+++ b/src/components/landing/BenefitsSection/types.ts
@@ -0,0 +1,14 @@
+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;
+ };
+};
\ No newline at end of file
diff --git a/src/components/landing/CtaButton/CtaButton.tsx b/src/components/landing/CtaButton/CtaButton.tsx
index 725c961..d4769e7 100644
--- a/src/components/landing/CtaButton/CtaButton.tsx
+++ b/src/components/landing/CtaButton/CtaButton.tsx
@@ -6,7 +6,7 @@ import { ButtonProps, Button } from '@chakra-ui/react';
import { URLs } from '../../../__data__/urls';
export const CtaButton: FC = (props) => {
- const { t } = useTranslation();
+ const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
return (
);
};
diff --git a/src/components/landing/HeroSection/HeroSection.tsx b/src/components/landing/HeroSection/HeroSection.tsx
index 9ae61c9..0a90622 100644
--- a/src/components/landing/HeroSection/HeroSection.tsx
+++ b/src/components/landing/HeroSection/HeroSection.tsx
@@ -1,22 +1,23 @@
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
-import { Box, Heading, Text, Center, VStack, BoxProps } from '@chakra-ui/react';
+import { Box, Heading, Text, Center, VStack } from '@chakra-ui/react';
import { DemoVideoPosterImg } from '../../../assets/images';
import { CtaButton, SiteLogo, PageSection } from '../';
-type HeroSectionProps = Pick;
+import { HeroSectionProps } from './types';
-export const HeroSection: FC = ({ flexShrink }) => {
- const { t } = useTranslation('~', {
- keyPrefix: 'dry-wash.landing.hero-section',
- });
+export const HeroSection: FC = ({
+ data: { headline, description, video } = {},
+ flexShrink,
+}) => {
+ const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
return (
= ({ flexShrink }) => {
color='white'
__css={{ textWrap: 'balance' }}
>
- {t('headline')}
+ {t(headline)}
- {t('description')}
+ {t(description)}
diff --git a/src/components/landing/HeroSection/types.ts b/src/components/landing/HeroSection/types.ts
new file mode 100644
index 0000000..ce2eb6a
--- /dev/null
+++ b/src/components/landing/HeroSection/types.ts
@@ -0,0 +1,9 @@
+import { BoxProps } from "@chakra-ui/react";
+
+export type HeroSectionProps = {
+ data: {
+ headline: 'hero-section.headline';
+ description: 'hero-section.description';
+ video: string;
+ };
+} & Pick;
\ No newline at end of file
diff --git a/src/components/landing/SocialProofSection/SocialProofSection.tsx b/src/components/landing/SocialProofSection/SocialProofSection.tsx
index fd49911..798ed67 100644
--- a/src/components/landing/SocialProofSection/SocialProofSection.tsx
+++ b/src/components/landing/SocialProofSection/SocialProofSection.tsx
@@ -5,15 +5,16 @@ import { Heading, HStack } from '@chakra-ui/react';
import { CtaButton, PageSection } from '../';
import { ReviewsSlider } from './ReviewsSlider';
+import { SocialProofSectionProps } from './types';
-export const SocialProofSection: FC = () => {
- const { t } = useTranslation('~', {
- keyPrefix: 'dry-wash.landing.social-proof-section',
- });
+export const SocialProofSection: FC = ({
+ data: { heading } = {},
+}) => {
+ const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
return (
- {t('heading')}
+ {t(heading)}
diff --git a/src/components/landing/SocialProofSection/index.ts b/src/components/landing/SocialProofSection/index.ts
index e20037f..2bdd48c 100644
--- a/src/components/landing/SocialProofSection/index.ts
+++ b/src/components/landing/SocialProofSection/index.ts
@@ -1 +1,2 @@
+export type { SocialProofSectionProps } from './types';
export { SocialProofSection } from './SocialProofSection';
\ No newline at end of file
diff --git a/src/components/landing/SocialProofSection/types.ts b/src/components/landing/SocialProofSection/types.ts
new file mode 100644
index 0000000..539208d
--- /dev/null
+++ b/src/components/landing/SocialProofSection/types.ts
@@ -0,0 +1,5 @@
+export type SocialProofSectionProps = {
+ data: {
+ heading: 'social-proof-section.heading';
+ };
+};
\ No newline at end of file
diff --git a/src/lib/index.ts b/src/lib/index.ts
new file mode 100644
index 0000000..fcdac2d
--- /dev/null
+++ b/src/lib/index.ts
@@ -0,0 +1 @@
+export * from './types';
\ No newline at end of file
diff --git a/src/lib/types.ts b/src/lib/types.ts
new file mode 100644
index 0000000..a2dbcc5
--- /dev/null
+++ b/src/lib/types.ts
@@ -0,0 +1,41 @@
+/**
+ * @example type Output = ArrElement<['a', 'b', 'c']>;
+ * // "a" | "b" | "c"
+ */
+export type ArrElement = ArrType extends readonly (infer ElementType)[]
+ ? ElementType
+ : never;
+
+/**
+ * @example type Output = Split<'a.b1' | 'a.b2', '.'>;
+ * // ["a", "b1"] | ["a", "b2"]
+*/
+type Split =
+ S extends `${infer A}${D}${infer B}` ? [A, ...Split] : [S];
+
+/**
+ * @example type Output = NestedObject<["a", "b1"] | ["a", "b2"]>;
+ * // { a: { b1: string; }; } | { a: { b2: string; }; }
+*/
+type NestedObject =
+ T extends [infer Head, ...infer Tail] ?
+ Head extends string ?
+ { [key in Head]: NestedObject } : never :
+ string;
+
+/**
+ * @example type Output = UnionToIntersection<{ a: { b1: string; }; } | { a: { b2: string; }; }>;
+ * // { a: { b1: string; }; } & { a: { b2: string; }; }
+*/
+type UnionToIntersection =
+ // 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 =
+ UnionToIntersection> : never : never>;
\ No newline at end of file
diff --git a/src/models/i18next.d.ts b/src/models/i18next.d.ts
index 7a1a687..7044fc2 100644
--- a/src/models/i18next.d.ts
+++ b/src/models/i18next.d.ts
@@ -1,24 +1,5 @@
import defaultLocale from '../../locales/ru.json';
-
-type Split =
- S extends `${infer A}${D}${infer B}` ? [A, ...Split] : [S];
-
-type NestedObject =
- T extends [infer Head, ...infer Tail] ?
- Head extends string ?
- { [key in Head]: NestedObject } : never :
- string;
-
-// Основная утилита для обработки union type
-type CreateTree =
- UnionToIntersection> : never : never>;
-
-// Утилита для объединения типов
-type UnionToIntersection =
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
+import { CreateTree } from '../lib';
type LanguageResource = CreateTree;
@@ -26,6 +7,6 @@ declare module "i18next" {
interface CustomTypeOptions {
resources: {
'~': LanguageResource
- };
+ }
}
}
\ No newline at end of file
diff --git a/src/models/landing/index.ts b/src/models/landing/index.ts
new file mode 100644
index 0000000..11add84
--- /dev/null
+++ b/src/models/landing/index.ts
@@ -0,0 +1 @@
+export * from './stubs';
\ No newline at end of file
diff --git a/src/models/landing/stubs/index.ts b/src/models/landing/stubs/index.ts
new file mode 100644
index 0000000..2484a17
--- /dev/null
+++ b/src/models/landing/stubs/index.ts
@@ -0,0 +1 @@
+export { default as LandingSuccessStub } from './success';
\ No newline at end of file
diff --git a/src/models/landing/stubs/success.ts b/src/models/landing/stubs/success.ts
new file mode 100644
index 0000000..319a3b6
--- /dev/null
+++ b/src/models/landing/stubs/success.ts
@@ -0,0 +1,27 @@
+// 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;
\ No newline at end of file
diff --git a/src/pages/landing/index.tsx b/src/pages/landing/index.tsx
index 62155e7..b86ef35 100644
--- a/src/pages/landing/index.tsx
+++ b/src/pages/landing/index.tsx
@@ -8,6 +8,12 @@ import {
SocialProofSection,
} from '../../components/landing';
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 = () => {
return (
@@ -21,10 +27,19 @@ const Page: FC = () => {
centerContent
>
-
+
-
-
+ {landingSuccessStub.body.sections.map(({ type, ...data }, i) => {
+ if (isBenefitsSectionData(type, data)) {
+ return ;
+ }
+ if (isSocialProofSectionData(type, data)) {
+ return ;
+ }
+ })}
diff --git a/src/pages/landing/types.ts b/src/pages/landing/types.ts
new file mode 100644
index 0000000..57e5638
--- /dev/null
+++ b/src/pages/landing/types.ts
@@ -0,0 +1,15 @@
+import LandingSuccess from "../../../stubs/json/landing/success.json";
+import { BenefitsSectionProps, SocialProofSectionProps } from "../../components/landing";
+import { ArrElement } from "../../lib";
+
+type SectionsItemData = ArrElement;
+type SectionType = SectionsItemData['type'];
+type SectionData = Omit;
+
+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';
+};
\ No newline at end of file
diff --git a/stubs/json/landing/success.json b/stubs/json/landing/success.json
new file mode 100644
index 0000000..d6ca8b6
--- /dev/null
+++ b/stubs/json/landing/success.json
@@ -0,0 +1,27 @@
+{
+ "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"
+ }
+ ]
+ }
+}
\ No newline at end of file