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 ; + } + })}