@redneckz/wildless-cms-uni-blocks 0.2.46 → 0.2.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/BenefitsBlock.d.ts +2 -2
- package/dist/Blocks.d.ts +15 -0
- package/dist/Blocks.js +13 -7
- package/dist/Blocks.js.map +1 -1
- package/dist/ContentPage.d.ts +2 -2
- package/dist/ContentPage.js +3 -3
- package/dist/ContentPage.js.map +1 -1
- package/dist/ContentPageContext.d.ts +6 -3
- package/dist/ExchangeRateTile.d.ts +7 -0
- package/dist/ExchangeRateTile.js +32 -0
- package/dist/ExchangeRateTile.js.map +1 -0
- package/dist/Footer/Contacts.d.ts +2 -2
- package/dist/Footer/Footer.d.ts +3 -4
- package/dist/Footer/Footer.js.map +1 -1
- package/dist/Footer/FooterLink.d.ts +2 -2
- package/dist/Footer/Sitemap.d.ts +2 -2
- package/dist/Footer/Sitemap.js +8 -5
- package/dist/Footer/Sitemap.js.map +1 -1
- package/dist/Footer/SocialMedia.d.ts +2 -2
- package/dist/Footer/SocialMedia.js +5 -5
- package/dist/Footer/SocialMedia.js.map +1 -1
- package/dist/Footer/index.d.ts +1 -1
- package/dist/Header/Header.d.ts +2 -2
- package/dist/Header/Header.js +3 -2
- package/dist/Header/Header.js.map +1 -1
- package/dist/Icons/DollarIcon.d.ts +1 -0
- package/dist/Icons/DollarIcon.js +6 -0
- package/dist/Icons/DollarIcon.js.map +1 -0
- package/dist/Icons/EuroIcon.d.ts +1 -0
- package/dist/Icons/EuroIcon.js +6 -0
- package/dist/Icons/EuroIcon.js.map +1 -0
- package/dist/Icons/index.d.ts +17 -16
- package/dist/Icons/index.js +26 -22
- package/dist/Icons/index.js.map +1 -1
- package/dist/Img.js +1 -1
- package/dist/Img.js.map +1 -1
- package/dist/LikeControl.d.ts +2 -2
- package/dist/LikeControl.js.map +1 -1
- package/dist/MobileAppTile.d.ts +8 -0
- package/dist/MobileAppTile.js +9 -0
- package/dist/MobileAppTile.js.map +1 -0
- package/dist/Placeholder.d.ts +3 -1
- package/dist/Placeholder.js +9 -2
- package/dist/Placeholder.js.map +1 -1
- package/dist/ProductBlock.d.ts +2 -2
- package/dist/ProductBlockInner.d.ts +2 -2
- package/dist/ProductBlockInner.js +2 -1
- package/dist/ProductBlockInner.js.map +1 -1
- package/dist/ProductGallery.d.ts +2 -2
- package/dist/ProductGallery.js +3 -3
- package/dist/ProductGallery.js.map +1 -1
- package/dist/ProductTile.d.ts +17 -0
- package/dist/ProductTile.js +27 -0
- package/dist/ProductTile.js.map +1 -0
- package/dist/ProjectSettings.d.ts +18 -0
- package/dist/ProjectSettings.js +30 -0
- package/dist/ProjectSettings.js.map +1 -0
- package/dist/StepsBlock.d.ts +2 -2
- package/dist/StepsBlock.js +7 -3
- package/dist/StepsBlock.js.map +1 -1
- package/dist/Tile.d.ts +2 -2
- package/dist/Tile.js +15 -7
- package/dist/Tile.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -10
- package/dist/ui-kit/SearchBar.d.ts +2 -2
- package/dist/ui-kit/SearchBar.js.map +1 -1
- package/dist/useSitemap.d.ts +3 -0
- package/dist/useSitemap.js +16 -0
- package/dist/useSitemap.js.map +1 -0
- package/dist/utils/joinList.d.ts +1 -0
- package/dist/utils/joinList.js +7 -0
- package/dist/utils/joinList.js.map +1 -0
- package/lib/BenefitsBlock.d.ts +2 -2
- package/lib/Blocks.d.ts +15 -0
- package/lib/Blocks.js +10 -4
- package/lib/Blocks.js.map +1 -1
- package/lib/ContentPage.d.ts +2 -2
- package/lib/ContentPage.js +3 -3
- package/lib/ContentPage.js.map +1 -1
- package/lib/ContentPageContext.d.ts +6 -3
- package/lib/ExchangeRateTile.d.ts +7 -0
- package/lib/ExchangeRateTile.js +30 -0
- package/lib/ExchangeRateTile.js.map +1 -0
- package/lib/Footer/Contacts.d.ts +2 -2
- package/lib/Footer/Footer.d.ts +3 -4
- package/lib/Footer/Footer.js.map +1 -1
- package/lib/Footer/FooterLink.d.ts +2 -2
- package/lib/Footer/Sitemap.d.ts +2 -2
- package/lib/Footer/Sitemap.js +8 -5
- package/lib/Footer/Sitemap.js.map +1 -1
- package/lib/Footer/SocialMedia.d.ts +2 -2
- package/lib/Footer/SocialMedia.js +5 -5
- package/lib/Footer/SocialMedia.js.map +1 -1
- package/lib/Footer/index.d.ts +1 -1
- package/lib/Header/Header.d.ts +2 -2
- package/lib/Header/Header.js +3 -2
- package/lib/Header/Header.js.map +1 -1
- package/lib/Icons/DollarIcon.d.ts +1 -0
- package/lib/Icons/DollarIcon.js +3 -0
- package/lib/Icons/DollarIcon.js.map +1 -0
- package/lib/Icons/EuroIcon.d.ts +1 -0
- package/lib/Icons/EuroIcon.js +3 -0
- package/lib/Icons/EuroIcon.js.map +1 -0
- package/lib/Icons/index.d.ts +17 -16
- package/lib/Icons/index.js +17 -16
- package/lib/Icons/index.js.map +1 -1
- package/lib/Img.js +1 -1
- package/lib/Img.js.map +1 -1
- package/lib/LikeControl.d.ts +2 -2
- package/lib/LikeControl.js.map +1 -1
- package/lib/MobileAppTile.d.ts +8 -0
- package/lib/MobileAppTile.js +7 -0
- package/lib/MobileAppTile.js.map +1 -0
- package/lib/Placeholder.d.ts +3 -1
- package/lib/Placeholder.js +9 -2
- package/lib/Placeholder.js.map +1 -1
- package/lib/ProductBlock.d.ts +2 -2
- package/lib/ProductBlockInner.d.ts +2 -2
- package/lib/ProductBlockInner.js +2 -1
- package/lib/ProductBlockInner.js.map +1 -1
- package/lib/ProductGallery.d.ts +2 -2
- package/lib/ProductGallery.js +3 -3
- package/lib/ProductGallery.js.map +1 -1
- package/lib/ProductTile.d.ts +17 -0
- package/lib/ProductTile.js +25 -0
- package/lib/ProductTile.js.map +1 -0
- package/lib/ProjectSettings.d.ts +18 -0
- package/lib/ProjectSettings.js +28 -0
- package/lib/ProjectSettings.js.map +1 -0
- package/lib/StepsBlock.d.ts +2 -2
- package/lib/StepsBlock.js +7 -3
- package/lib/StepsBlock.js.map +1 -1
- package/lib/Tile.d.ts +2 -2
- package/lib/Tile.js +15 -7
- package/lib/Tile.js.map +1 -1
- package/lib/common.css +1 -1
- package/lib/index.d.ts +6 -3
- package/lib/index.js +4 -2
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +3 -10
- package/lib/ui-kit/SearchBar.d.ts +2 -2
- package/lib/ui-kit/SearchBar.js.map +1 -1
- package/lib/useSitemap.d.ts +3 -0
- package/lib/useSitemap.js +13 -0
- package/lib/useSitemap.js.map +1 -0
- package/lib/utils/joinList.d.ts +1 -0
- package/lib/utils/joinList.js +4 -0
- package/lib/utils/joinList.js.map +1 -0
- package/package.json +3 -2
- package/src/BenefitsBlock.fixture.tsx +34 -0
- package/src/BenefitsBlock.tsx +45 -0
- package/src/Blocks.ts +39 -0
- package/src/ContentPage.fixture.tsx +38 -0
- package/src/ContentPage.page.json +680 -0
- package/src/ContentPage.tsx +83 -0
- package/src/ContentPageContext.tsx +34 -0
- package/src/ContentPageHead.tsx +26 -0
- package/src/ExchangeRateTile.fixture.tsx +11 -0
- package/src/ExchangeRateTile.tsx +80 -0
- package/src/Footer/Contacts.tsx +71 -0
- package/src/Footer/Footer.fixture.tsx +265 -0
- package/src/Footer/Footer.tsx +47 -0
- package/src/Footer/FooterLink.ts +6 -0
- package/src/Footer/HorizontalNavigation.tsx +46 -0
- package/src/Footer/Sitemap.tsx +57 -0
- package/src/Footer/SocialMedia.tsx +46 -0
- package/src/Footer/TextInformation.tsx +50 -0
- package/src/Footer/index.ts +2 -0
- package/src/Header/Header.fixture.tsx +21 -0
- package/src/Header/Header.tsx +55 -0
- package/src/Header/HeaderContent.ts +6 -0
- package/src/Header/HeaderSecondaryMenu.tsx +42 -0
- package/src/Header/HeaderSecondaryMenuButton.tsx +21 -0
- package/src/Header/index.ts +3 -0
- package/src/Header/isSubItemActive.ts +9 -0
- package/src/Header/isTopItemActive.ts +13 -0
- package/src/Icons/ActualBalanceIcon.tsx +202 -0
- package/src/Icons/CalendarIcon.tsx +19 -0
- package/src/Icons/ClockIcon.tsx +212 -0
- package/src/Icons/ComfortableCompIcon.tsx +379 -0
- package/src/Icons/DollarIcon.tsx +12 -0
- package/src/Icons/EmptyWalletIcon.tsx +25 -0
- package/src/Icons/EuroIcon.tsx +12 -0
- package/src/Icons/GridIcon.tsx +9 -0
- package/src/Icons/LoupeIcon.tsx +11 -0
- package/src/Icons/NewDocIcon.tsx +262 -0
- package/src/Icons/OkIcon.tsx +8 -0
- package/src/Icons/PassSendIcon.tsx +250 -0
- package/src/Icons/PercentageSquareIcon.tsx +18 -0
- package/src/Icons/ProfileIcon.tsx +18 -0
- package/src/Icons/SignDocsIcon.tsx +295 -0
- package/src/Icons/TelegramIcon.tsx +8 -0
- package/src/Icons/VKIcon.tsx +10 -0
- package/src/Icons/index.ts +17 -0
- package/src/Img.tsx +22 -0
- package/src/LikeControl.fixture.tsx +5 -0
- package/src/LikeControl.tsx +23 -0
- package/src/MobileAppTile.fixture.tsx +15 -0
- package/src/MobileAppTile.tsx +34 -0
- package/src/Placeholder.fixture.tsx +5 -0
- package/src/Placeholder.tsx +33 -0
- package/src/ProductBlock.fixture.tsx +95 -0
- package/src/ProductBlock.tsx +15 -0
- package/src/ProductBlockInner.tsx +111 -0
- package/src/ProductGallery.fixture.tsx +138 -0
- package/src/ProductGallery.tsx +88 -0
- package/src/ProductTile.fixture.tsx +147 -0
- package/src/ProductTile.tsx +95 -0
- package/src/ProjectSettings.ts +38 -0
- package/src/StepsBlock.fixture.tsx +23 -0
- package/src/StepsBlock.tsx +65 -0
- package/src/Tile.fixture.tsx +124 -0
- package/src/Tile.tsx +89 -0
- package/src/api/LikeAPI.ts +141 -0
- package/src/common.css +39 -0
- package/src/content-page-repository/ContentPageRepository.ts +38 -0
- package/src/content-page-repository/TransformationOptions.ts +4 -0
- package/src/content-page-repository/index.ts +1 -0
- package/src/content-page-repository/mapJSON.spec.ts +28 -0
- package/src/content-page-repository/mapJSON.ts +23 -0
- package/src/content-page-repository/transformContentPage.ts +26 -0
- package/src/content-page-repository/transformMarkdown.ts +10 -0
- package/src/content-page-repository/transformPicture.ts +61 -0
- package/src/cosmos.decorator.tsx +19 -0
- package/src/index.ts +20 -0
- package/src/mergeTopItems.spec.ts +20 -0
- package/src/mergeTopItems.ts +13 -0
- package/src/setup-fixture.ts +49 -0
- package/src/types.ts +94 -0
- package/src/ui-kit/BlockItem.fixture.tsx +5 -0
- package/src/ui-kit/BlockItem.tsx +36 -0
- package/src/ui-kit/Breadcrumb.tsx +26 -0
- package/src/ui-kit/Button.fixture.tsx +113 -0
- package/src/ui-kit/Button.tsx +66 -0
- package/src/ui-kit/HeaderItem.fixture.tsx +8 -0
- package/src/ui-kit/HeaderItem.tsx +25 -0
- package/src/ui-kit/LikeButton.fixture.tsx +6 -0
- package/src/ui-kit/LikeButton.tsx +41 -0
- package/src/ui-kit/Logo.fixture.tsx +5 -0
- package/src/ui-kit/Logo.tsx +35 -0
- package/src/ui-kit/SVG.tsx +32 -0
- package/src/ui-kit/SearchBar.tsx +33 -0
- package/src/ui-kit/TopItem.fixture.tsx +8 -0
- package/src/ui-kit/TopItem.tsx +37 -0
- package/src/useLink.ts +28 -0
- package/src/useSitemap.ts +17 -0
- package/src/utils/joinList.ts +7 -0
- package/src/utils/url.ts +22 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { JSX } from '@redneckz/uni-jsx';
|
|
2
|
+
import { NewDocIcon, PassSendIcon, ActualBalanceIcon } from './Icons/index';
|
|
3
|
+
import type { UniBlockProps } from './types';
|
|
4
|
+
import { joinList } from './utils/joinList';
|
|
5
|
+
|
|
6
|
+
const ICONS = { NewDocIcon, PassSendIcon, ActualBalanceIcon };
|
|
7
|
+
export interface Step {
|
|
8
|
+
label: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
icon?: keyof typeof ICONS;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface StepsBlockContent {
|
|
14
|
+
title?: string;
|
|
15
|
+
steps?: Step[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StepsBlockProps extends StepsBlockContent, UniBlockProps {}
|
|
19
|
+
|
|
20
|
+
export const StepsBlock = JSX<StepsBlockProps>(({ className, title, steps }) => {
|
|
21
|
+
return (
|
|
22
|
+
<section
|
|
23
|
+
className={`font-sans text-primary-text bg-white px-12 py-12 flex flex-col items-center ${
|
|
24
|
+
className || ''
|
|
25
|
+
}`}
|
|
26
|
+
>
|
|
27
|
+
<h2 className="font-medium text-title m-0 max-w-[47rem] text-center">{title}</h2>
|
|
28
|
+
{steps?.length ? (
|
|
29
|
+
<div>
|
|
30
|
+
<div className="flex mt-9 items-center px-[88px]">
|
|
31
|
+
{joinList(<div className="h-0.5 w-full bg-secondary-light"></div>)(
|
|
32
|
+
steps.map((step, i) => renderStepIcon(step, i)),
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
<div className="flex mt-9 justify-between gap-x-24">{steps.map(renderStepTitle)}</div>
|
|
36
|
+
</div>
|
|
37
|
+
) : null}
|
|
38
|
+
</section>
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const renderStepIcon = (step: Step, i: number) => {
|
|
43
|
+
return (
|
|
44
|
+
<div key={String(i)} className="flex flex-col items-center text-center relative">
|
|
45
|
+
<div className="h-[100px] w-[100px] min-w-[100px] min-h-[100px] bg-secondary-light rounded-full p-[26px] box-border z-10">
|
|
46
|
+
{(step.icon && ICONS[step.icon]?.({ width: 48, height: 48 })) || (
|
|
47
|
+
<span className="font-medium text-title-sm text-secondary-text">{i + 1}</span>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const renderStepTitle = (step: Step, i: number) => {
|
|
55
|
+
return (
|
|
56
|
+
<div key={String(i)} className="flex flex-col items-center text-center relative w-[276px]">
|
|
57
|
+
<div className="max-w-min">
|
|
58
|
+
<h5 className="font-medium text-xl m-0 mt-4 whitespace-nowrap px-3">{step.label}</h5>
|
|
59
|
+
{step.description && (
|
|
60
|
+
<div className="font-normal text-sm text-secondary-text mt-2">{step.description}</div>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { context } from './setup-fixture';
|
|
2
|
+
|
|
3
|
+
import { Tile } from './Tile';
|
|
4
|
+
import { Picture } from './types';
|
|
5
|
+
|
|
6
|
+
const image: Picture = {
|
|
7
|
+
src: 'money-1.png',
|
|
8
|
+
format: 'webp',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const image2: Picture = {
|
|
12
|
+
src: 'credit-card-1.png',
|
|
13
|
+
format: 'webp',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const image3: Picture = {
|
|
17
|
+
src: 'column-1.png',
|
|
18
|
+
format: 'webp',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const image4: Picture = {
|
|
22
|
+
src: 'globe-1.png',
|
|
23
|
+
format: 'webp',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
credit: (
|
|
28
|
+
<div className="grid grid-cols-12">
|
|
29
|
+
<Tile
|
|
30
|
+
context={context}
|
|
31
|
+
className="col-span-8"
|
|
32
|
+
title="Кредиты"
|
|
33
|
+
items={[
|
|
34
|
+
'Кредитный лимит до 10 млн ₽',
|
|
35
|
+
'Кредиты наличными на любые цели',
|
|
36
|
+
'Получите деньги без залога и поручителей',
|
|
37
|
+
]}
|
|
38
|
+
button={{
|
|
39
|
+
text: 'Подробнее',
|
|
40
|
+
href: '/credit-cards',
|
|
41
|
+
version: 'secondary',
|
|
42
|
+
}}
|
|
43
|
+
image={image}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
),
|
|
47
|
+
mortgage: (
|
|
48
|
+
<div className="grid grid-cols-12">
|
|
49
|
+
<Tile
|
|
50
|
+
context={context}
|
|
51
|
+
version="secondary"
|
|
52
|
+
className="col-span-4"
|
|
53
|
+
title="Ипотека"
|
|
54
|
+
description={
|
|
55
|
+
'Широкий выбор ипотечных программ с государственной поддержкой. Специальные предложения от 12,2 %'
|
|
56
|
+
}
|
|
57
|
+
button={{
|
|
58
|
+
href: 'https://rshb.ru',
|
|
59
|
+
text: 'Подробнее',
|
|
60
|
+
target: '_blank',
|
|
61
|
+
version: 'secondary',
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
),
|
|
66
|
+
'credit-card': (
|
|
67
|
+
<div className="grid grid-cols-12">
|
|
68
|
+
<Tile
|
|
69
|
+
context={context}
|
|
70
|
+
className="col-span-6"
|
|
71
|
+
title="Банковские пластиковые карты"
|
|
72
|
+
items={['Счета в разных валютах', 'Бесплатное обслуживание', 'Кэшбэк до 15% от покупок']}
|
|
73
|
+
button={{
|
|
74
|
+
href: 'https://rshb.ru',
|
|
75
|
+
text: 'Подробнее',
|
|
76
|
+
target: '_blank',
|
|
77
|
+
version: 'secondary',
|
|
78
|
+
}}
|
|
79
|
+
image={image2}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
),
|
|
83
|
+
premium: (
|
|
84
|
+
<div className="grid grid-cols-12">
|
|
85
|
+
<Tile
|
|
86
|
+
context={context}
|
|
87
|
+
className="col-span-6"
|
|
88
|
+
title="Премиальные продукты"
|
|
89
|
+
description={
|
|
90
|
+
'Выделенные зоны обслуживания и персональный финансовый консультант. Полный спектр премиальных продуктов, подчеркивающих ваш статус'
|
|
91
|
+
}
|
|
92
|
+
button={{
|
|
93
|
+
href: 'https://rshb.ru',
|
|
94
|
+
text: 'Подробнее',
|
|
95
|
+
target: '_blank',
|
|
96
|
+
version: 'secondary',
|
|
97
|
+
}}
|
|
98
|
+
image={image3}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
),
|
|
102
|
+
ecosystem: (
|
|
103
|
+
<div className="grid grid-cols-12">
|
|
104
|
+
<Tile
|
|
105
|
+
context={context}
|
|
106
|
+
className="col-span-12 pr-28"
|
|
107
|
+
title="Экосистема «СВОЕ», созданная для людей"
|
|
108
|
+
items={[
|
|
109
|
+
'Качественные фермерские продукты напрямую от производителей',
|
|
110
|
+
'Самая большая база сельских туров по России',
|
|
111
|
+
'Площадки для создания комфортной жизни за городом',
|
|
112
|
+
'Подробная база вакансий для жителей сельской местности',
|
|
113
|
+
]}
|
|
114
|
+
button={{
|
|
115
|
+
href: 'https://rshb.ru',
|
|
116
|
+
text: 'Подробнее',
|
|
117
|
+
target: '_blank',
|
|
118
|
+
version: 'secondary',
|
|
119
|
+
}}
|
|
120
|
+
image={image4}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
),
|
|
124
|
+
};
|
package/src/Tile.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { JSX } from '@redneckz/uni-jsx';
|
|
2
|
+
import { Img } from './Img';
|
|
3
|
+
import type { BlockVersion, Picture, UniBlockProps } from './types';
|
|
4
|
+
import { BlockItem } from './ui-kit/BlockItem';
|
|
5
|
+
import type { ButtonProps } from './ui-kit/Button';
|
|
6
|
+
import { Button } from './ui-kit/Button';
|
|
7
|
+
import { useLink } from './useLink';
|
|
8
|
+
|
|
9
|
+
export interface TileContent {
|
|
10
|
+
title?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
image?: Picture;
|
|
13
|
+
items?: string[];
|
|
14
|
+
button?: ButtonProps;
|
|
15
|
+
version?: BlockVersion;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TileProps extends TileContent, UniBlockProps {}
|
|
19
|
+
|
|
20
|
+
const tileStyleMap: Record<BlockVersion, string> = {
|
|
21
|
+
primary: 'bg-white text-primary-text',
|
|
22
|
+
secondary: 'bg-primary-main text-white',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const TITLE_CLASSES = 'font-medium m-0 whitespace-pre-wrap max-w-[600px]';
|
|
26
|
+
|
|
27
|
+
export const Tile = JSX<TileProps>(
|
|
28
|
+
({
|
|
29
|
+
className,
|
|
30
|
+
context,
|
|
31
|
+
title,
|
|
32
|
+
description,
|
|
33
|
+
children,
|
|
34
|
+
button,
|
|
35
|
+
image,
|
|
36
|
+
items,
|
|
37
|
+
version = 'primary',
|
|
38
|
+
}) => {
|
|
39
|
+
const router = context.useRouter();
|
|
40
|
+
const { handlerDecorator } = context;
|
|
41
|
+
return (
|
|
42
|
+
<section
|
|
43
|
+
className={`flex flex-col font-sans p-9 ${className || ''} ${tileStyleMap[version]}`}
|
|
44
|
+
>
|
|
45
|
+
{title && renderTitle(title, className)}
|
|
46
|
+
<div className="grow flex justify-between mt-6">
|
|
47
|
+
<div className="flex flex-col justify-between items-start">
|
|
48
|
+
{description || children ? (
|
|
49
|
+
<div className="font-normal text-base max-w-[600px]">{description || children}</div>
|
|
50
|
+
) : (
|
|
51
|
+
renderItems(items, version)
|
|
52
|
+
)}
|
|
53
|
+
{button?.text && (
|
|
54
|
+
<Button
|
|
55
|
+
className="mt-6"
|
|
56
|
+
{...useLink({ router, handlerDecorator }, button)}
|
|
57
|
+
version="secondary"
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
{image?.src && <Img className="mt-auto h-[220px] w-[220px]" image={image} />}
|
|
62
|
+
</div>
|
|
63
|
+
</section>
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
function renderTitle(title: string, className: string = '') {
|
|
69
|
+
const colSpanPrefix = 'col-span-';
|
|
70
|
+
const colSpanStr = className.split(/\s+/).find((_) => _.startsWith(colSpanPrefix));
|
|
71
|
+
const colSpan = colSpanStr ? parseInt(colSpanStr.substring(colSpanPrefix.length), 10) : 12;
|
|
72
|
+
if (colSpan <= 4) {
|
|
73
|
+
return <h3 className={`${TITLE_CLASSES} text-title-xs`}>{title}</h3>;
|
|
74
|
+
} else if (colSpan <= 8) {
|
|
75
|
+
return <h3 className={`${TITLE_CLASSES} text-title-sm`}>{title}</h3>;
|
|
76
|
+
} else {
|
|
77
|
+
return <h2 className={`${TITLE_CLASSES} text-title`}>{title}</h2>;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderItems(items?: string[], version?: BlockVersion) {
|
|
82
|
+
return items?.length ? (
|
|
83
|
+
<section className="max-w-[600px]" role="list">
|
|
84
|
+
{items.map((_, i) => (
|
|
85
|
+
<BlockItem key={String(i)} className={i ? 'mt-2.5' : ''} text={_} version={version} />
|
|
86
|
+
))}
|
|
87
|
+
</section>
|
|
88
|
+
) : null;
|
|
89
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
interface UserData {
|
|
2
|
+
token: string;
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
export interface LikeData {
|
|
6
|
+
node_id: string;
|
|
7
|
+
value: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const AUTH_PREFIX = 'guest';
|
|
11
|
+
const STORAGE_KEY = 'likesToken';
|
|
12
|
+
|
|
13
|
+
export function LikeAPI(projectId: string, baseURL: string) {
|
|
14
|
+
async function getLikeCount(...nodeIds: string[]): Promise<LikeData[]> {
|
|
15
|
+
if (!nodeIds.length) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { token: Authorization } = await auth();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(`/assist/v1/public/projects/${projectId}/nodes/likes`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
mode: 'cors',
|
|
25
|
+
headers: { 'Content-Type': 'application/json', Authorization },
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
node_ids: nodeIds,
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const { likes } = await response.json();
|
|
32
|
+
|
|
33
|
+
return likes || [];
|
|
34
|
+
} catch (e) {
|
|
35
|
+
console.error(e);
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function like(nodeId: string): Promise<number> {
|
|
41
|
+
if (!nodeId) {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { token: Authorization, name } = await auth();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch(`${baseURL}/assist/v1/public/projects/${projectId}/nodes/like`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
mode: 'cors',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
Authorization,
|
|
54
|
+
UserID: name,
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
node_id: nodeId,
|
|
58
|
+
user_name: name,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
const { likes } = await response.json();
|
|
62
|
+
|
|
63
|
+
return likes || 0;
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error(e);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function dislike(nodeId: string): Promise<number> {
|
|
71
|
+
if (!nodeId) {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const { token: Authorization, name } = await auth();
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch(
|
|
79
|
+
`${baseURL}/assist/v1/public/projects/${projectId}/nodes/unlike`,
|
|
80
|
+
{
|
|
81
|
+
method: 'POST',
|
|
82
|
+
mode: 'cors',
|
|
83
|
+
headers: {
|
|
84
|
+
'Content-Type': 'application/json',
|
|
85
|
+
Authorization,
|
|
86
|
+
UserID: name,
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
node_id: nodeId,
|
|
90
|
+
user_name: name,
|
|
91
|
+
}),
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
const { likes } = await response.json();
|
|
95
|
+
|
|
96
|
+
return likes || 0;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.error(e);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function auth(): Promise<UserData> {
|
|
104
|
+
try {
|
|
105
|
+
const userData: UserData = JSON.parse(globalThis.localStorage.getItem(STORAGE_KEY) || '');
|
|
106
|
+
if (userData?.token) {
|
|
107
|
+
return userData;
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// do nothing
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(`${baseURL}/assist/v1/public/auth`, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
mode: 'cors',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
prefix: AUTH_PREFIX,
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const { token, name } = await response.json();
|
|
126
|
+
|
|
127
|
+
globalThis.localStorage.setItem(STORAGE_KEY, JSON.stringify({ token, name }));
|
|
128
|
+
|
|
129
|
+
return { token, name } as UserData;
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.error(e);
|
|
132
|
+
return {} as UserData;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
getLikeCount,
|
|
138
|
+
like,
|
|
139
|
+
dislike,
|
|
140
|
+
};
|
|
141
|
+
}
|
package/src/common.css
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@import '../fonts/ubuntu/ubuntu.css';
|
|
2
|
+
|
|
3
|
+
/* Private clients | Green */
|
|
4
|
+
[data-theme='pc'] {
|
|
5
|
+
--color-primary-main: #42ab44;
|
|
6
|
+
--color-primary-hover: #248341;
|
|
7
|
+
--color-primary-active: #245f34;
|
|
8
|
+
--color-secondary-hover: #c5e2c6;
|
|
9
|
+
--color-secondary-active: #d8f2d9;
|
|
10
|
+
}
|
|
11
|
+
/* Business clients | Light-Green */
|
|
12
|
+
[data-theme='bc'] {
|
|
13
|
+
--color-primary-main: #83b83e;
|
|
14
|
+
--color-primary-hover: #5c921d;
|
|
15
|
+
--color-primary-active: #3c5e10;
|
|
16
|
+
--color-secondary-hover: #e8f4d9;
|
|
17
|
+
--color-secondary-active: #d8e3ca;
|
|
18
|
+
}
|
|
19
|
+
/* Ecosystem own | Yellow */
|
|
20
|
+
[data-theme='eo'] {
|
|
21
|
+
--color-primary-main: #ddb63d;
|
|
22
|
+
--color-primary-hover: #bb9727;
|
|
23
|
+
--color-primary-active: #7e630c;
|
|
24
|
+
--color-secondary-hover: #f1ecdd;
|
|
25
|
+
--color-secondary-active: #e5dec7;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@tailwind components;
|
|
29
|
+
@tailwind utilities;
|
|
30
|
+
|
|
31
|
+
@layer utilities {
|
|
32
|
+
.rotate-180 {
|
|
33
|
+
transform: rotate(180deg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.-translate-x-full {
|
|
37
|
+
transform: translateX(-100%);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import glob from 'glob';
|
|
2
|
+
import util from 'util';
|
|
3
|
+
import type { ContentPageDef } from '../types';
|
|
4
|
+
import type { TransformationOptions } from './TransformationOptions';
|
|
5
|
+
import { transformContentPage } from './transformContentPage';
|
|
6
|
+
|
|
7
|
+
const find = util.promisify(glob);
|
|
8
|
+
|
|
9
|
+
export function ContentPageRepository({
|
|
10
|
+
contentDir = 'content',
|
|
11
|
+
publicDir = 'public',
|
|
12
|
+
}: Partial<TransformationOptions> = {}) {
|
|
13
|
+
async function getAllContentPages(): Promise<ContentPageDef[]> {
|
|
14
|
+
const pagePaths = await find(`${contentDir}/**/*.page.json`);
|
|
15
|
+
return await Promise.all(pagePaths.map(readPage));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getContentPageBySlug(slug: string): Promise<ContentPageDef | undefined> {
|
|
19
|
+
const pages = await getAllContentPages();
|
|
20
|
+
return pages.find((content) => content.slug === slug);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const cache: Record<string, ContentPageDef> = {};
|
|
24
|
+
async function readPage(filePath: string): Promise<ContentPageDef> {
|
|
25
|
+
if (!(filePath in cache)) {
|
|
26
|
+
cache[filePath] = await transformContentPage(filePath, {
|
|
27
|
+
contentDir,
|
|
28
|
+
publicDir,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return cache[filePath];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
getAllContentPages,
|
|
36
|
+
getContentPageBySlug,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ContentPageRepository } from './ContentPageRepository';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { mapJSON } from './mapJSON';
|
|
2
|
+
|
|
3
|
+
describe('mapJSON', () => {
|
|
4
|
+
it('should map scalars', async () => {
|
|
5
|
+
const mapper = (_: any) => (typeof _ === 'string' ? '<MAPPED>' : _);
|
|
6
|
+
expect(await mapJSON('foo', mapper)).toBe('<MAPPED>');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should map arrays', async () => {
|
|
10
|
+
const mapper = (_: any) => (Array.isArray(_) ? _.map((item) => item.toUpperCase()) : _);
|
|
11
|
+
expect(await mapJSON(['foo', 'bar', 'baz'], mapper)).toEqual(['FOO', 'BAR', 'BAZ']);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should map objects', async () => {
|
|
15
|
+
const mapper = (_: any) => ('bar' in _ ? { ..._, bar: '<MAPPED>' } : _);
|
|
16
|
+
expect(await mapJSON({ foo: { bar: 'baz' } }, mapper)).toEqual({ foo: { bar: '<MAPPED>' } });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should support async mappers', async () => {
|
|
20
|
+
const mapper = (_: any) => ('bar' in _ ? Promise.resolve({ ..._, bar: '<MAPPED>' }) : _);
|
|
21
|
+
expect(await mapJSON({ foo: { bar: 'baz' } }, mapper)).toEqual({ foo: { bar: '<MAPPED>' } });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should map nil values as is', async () => {
|
|
25
|
+
const mapper = (_: any) => (!_ ? '<EMPTY>' : _);
|
|
26
|
+
expect(await mapJSON([null, undefined, ''], mapper)).toEqual([null, undefined, '<EMPTY>']);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export async function mapJSON<T>(
|
|
2
|
+
data: T,
|
|
3
|
+
mapper: <In, Out = In>(value: In, key: string[]) => Promise<Out>,
|
|
4
|
+
key: string[] = [],
|
|
5
|
+
): Promise<T> {
|
|
6
|
+
if (data === null || data === undefined) return data;
|
|
7
|
+
|
|
8
|
+
const mappedData = await mapper(data, key);
|
|
9
|
+
if (mappedData !== data) return mappedData as T;
|
|
10
|
+
|
|
11
|
+
if (Array.isArray(data)) {
|
|
12
|
+
const values = data.map((_, i) => mapJSON(_, mapper, [String(i)].concat(key)));
|
|
13
|
+
return Promise.all(values) as any;
|
|
14
|
+
} else if (typeof data === 'object' && (data as any).toString() === '[object Object]') {
|
|
15
|
+
const subKeys = Object.keys(data as any);
|
|
16
|
+
const values = subKeys.map((k) => mapJSON(data[k], mapper, [k].concat(key)));
|
|
17
|
+
const pairs = (await Promise.all(values)).map((_, i) => ({
|
|
18
|
+
[subKeys[i]]: _,
|
|
19
|
+
}));
|
|
20
|
+
return Object.assign({}, ...pairs);
|
|
21
|
+
}
|
|
22
|
+
return mapper<T>(data, key);
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import util from 'util';
|
|
3
|
+
import type { ContentPageDef, Picture } from '../types';
|
|
4
|
+
import { mapJSON } from './mapJSON';
|
|
5
|
+
import type { TransformationOptions } from './TransformationOptions';
|
|
6
|
+
import { transformMarkdown } from './transformMarkdown';
|
|
7
|
+
import { transformPicture } from './transformPicture';
|
|
8
|
+
|
|
9
|
+
const readFile = util.promisify(fs.readFile);
|
|
10
|
+
|
|
11
|
+
export async function transformContentPage(
|
|
12
|
+
pagePath: string,
|
|
13
|
+
options: TransformationOptions,
|
|
14
|
+
): Promise<ContentPageDef> {
|
|
15
|
+
const content = await readFile(pagePath, 'utf8');
|
|
16
|
+
return mapJSON<ContentPageDef>(JSON.parse(content), async (value: any, [key]) => {
|
|
17
|
+
const isMarkdown = typeof value === 'string' && key?.endsWith('__md');
|
|
18
|
+
const isPicture = value && value.src && typeof value.src === 'string';
|
|
19
|
+
if (isMarkdown) {
|
|
20
|
+
return await transformMarkdown(value);
|
|
21
|
+
} else if (isPicture) {
|
|
22
|
+
return await transformPicture(pagePath, value as Picture, options);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { unified } from 'unified';
|
|
2
|
+
import remarkParse from 'remark-parse';
|
|
3
|
+
import remarkHtml from 'remark-html';
|
|
4
|
+
|
|
5
|
+
export async function transformMarkdown(markdown: string | null | undefined): Promise<string> {
|
|
6
|
+
if (!markdown) return '';
|
|
7
|
+
|
|
8
|
+
const result = await unified().use(remarkParse).use(remarkHtml).process(markdown);
|
|
9
|
+
return result.toString('utf-8');
|
|
10
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import sharp from 'sharp';
|
|
4
|
+
import util from 'util';
|
|
5
|
+
import type { ImgSource, Picture } from '../types';
|
|
6
|
+
import type { TransformationOptions } from './TransformationOptions';
|
|
7
|
+
|
|
8
|
+
const mkdir = util.promisify(fs.mkdir);
|
|
9
|
+
|
|
10
|
+
export async function transformPicture(
|
|
11
|
+
pagePath: string,
|
|
12
|
+
picture: Picture,
|
|
13
|
+
options: TransformationOptions,
|
|
14
|
+
): Promise<Picture> {
|
|
15
|
+
const sources = picture.sources || [];
|
|
16
|
+
const [transformedPicture, ...transformedSources] = await Promise.all(
|
|
17
|
+
[picture].concat(sources).map((_) => transformImg(pagePath, picture.src, { ...options, ..._ })),
|
|
18
|
+
);
|
|
19
|
+
return {
|
|
20
|
+
...picture,
|
|
21
|
+
src: transformedPicture,
|
|
22
|
+
sources: transformedSources.map((_, i) => ({
|
|
23
|
+
...sources[i],
|
|
24
|
+
src: _,
|
|
25
|
+
})),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function transformImg(
|
|
30
|
+
pagePath: string,
|
|
31
|
+
src: string,
|
|
32
|
+
transformationOptions: TransformationOptions & ImgSource,
|
|
33
|
+
): Promise<string> {
|
|
34
|
+
const { contentDir, publicDir, format, options, size } = transformationOptions;
|
|
35
|
+
|
|
36
|
+
const imgPath = `${path.dirname(pagePath)}/${src}`;
|
|
37
|
+
let chain = sharp(imgPath);
|
|
38
|
+
if (size) chain = chain.resize(size);
|
|
39
|
+
if (format) chain = chain.toFormat(format, options);
|
|
40
|
+
|
|
41
|
+
const transformedSrc = transformSrc(src, transformationOptions);
|
|
42
|
+
const transformedImgPath = `${publicDir}/${path.relative(
|
|
43
|
+
contentDir,
|
|
44
|
+
`${path.dirname(pagePath)}/${transformedSrc}`,
|
|
45
|
+
)}`;
|
|
46
|
+
|
|
47
|
+
await mkdir(path.dirname(transformedImgPath), { recursive: true });
|
|
48
|
+
await chain.toFile(transformedImgPath);
|
|
49
|
+
|
|
50
|
+
return transformedSrc;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function transformSrc(src: string, { format, size }: TransformationOptions & ImgSource): string {
|
|
54
|
+
const noTransform = !format && !size;
|
|
55
|
+
if (noTransform) return src;
|
|
56
|
+
|
|
57
|
+
const suffix = [size?.width, size?.height].filter(Boolean).join('-');
|
|
58
|
+
return `${path.basename(src, path.extname(src))}${suffix ? `-${suffix}` : ''}.${
|
|
59
|
+
String(format) || path.extname(src)
|
|
60
|
+
}`;
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default ({ children }) => {
|
|
2
|
+
document.querySelector(':root')?.setAttribute('lang', 'ru'); // for lighthouse accessibility metrics
|
|
3
|
+
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
style={{
|
|
7
|
+
backgroundColor: 'rgba(51, 51, 51, 0.07)',
|
|
8
|
+
position: 'absolute',
|
|
9
|
+
left: 0,
|
|
10
|
+
right: 0,
|
|
11
|
+
bottom: 0,
|
|
12
|
+
top: 0,
|
|
13
|
+
}}
|
|
14
|
+
data-theme="pc"
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
};
|