@cloud-ru/uikit-product-calculator 0.33.3

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.
Files changed (205) hide show
  1. package/CHANGELOG.md +1508 -0
  2. package/LICENSE +201 -0
  3. package/README.md +85 -0
  4. package/package.json +75 -0
  5. package/src/components/Calculator/Calculator.tsx +79 -0
  6. package/src/components/Calculator/index.ts +1 -0
  7. package/src/components/CalculatorContent/CalculatorContent.tsx +38 -0
  8. package/src/components/CalculatorContent/index.ts +1 -0
  9. package/src/components/CalculatorContent/styles.module.scss +79 -0
  10. package/src/components/Catalog/Catalog.tsx +225 -0
  11. package/src/components/Catalog/components/PrivateCardHeader.tsx +63 -0
  12. package/src/components/Catalog/components/index.ts +1 -0
  13. package/src/components/Catalog/components/styles.module.scss +35 -0
  14. package/src/components/Catalog/index.ts +1 -0
  15. package/src/components/Catalog/styles.module.scss +104 -0
  16. package/src/components/Controls/AlertControl/AlertControl.tsx +44 -0
  17. package/src/components/Controls/AlertControl/index.ts +1 -0
  18. package/src/components/Controls/ArrayControl/ArrayControl.tsx +105 -0
  19. package/src/components/Controls/ArrayControl/index.ts +1 -0
  20. package/src/components/Controls/ArrayControl/styles.module.scss +33 -0
  21. package/src/components/Controls/CarouselControl/CarouselControl.tsx +173 -0
  22. package/src/components/Controls/CarouselControl/index.ts +1 -0
  23. package/src/components/Controls/CarouselControl/styles.module.scss +32 -0
  24. package/src/components/Controls/Control/Control.tsx +205 -0
  25. package/src/components/Controls/Control/index.ts +1 -0
  26. package/src/components/Controls/ObjectControl/ObjectControl.tsx +150 -0
  27. package/src/components/Controls/ObjectControl/index.ts +1 -0
  28. package/src/components/Controls/ObjectControl/styles.module.scss +18 -0
  29. package/src/components/Controls/ObjectDecorator/ObjectDecorator.tsx +23 -0
  30. package/src/components/Controls/ObjectDecorator/index.ts +1 -0
  31. package/src/components/Controls/ObjectDecorator/styles.module.scss +15 -0
  32. package/src/components/Controls/SegmentedControl/SegmentedControl.tsx +79 -0
  33. package/src/components/Controls/SegmentedControl/SegmentedControlCard.tsx +38 -0
  34. package/src/components/Controls/SegmentedControl/index.ts +1 -0
  35. package/src/components/Controls/SegmentedControl/styles.module.scss +8 -0
  36. package/src/components/Controls/SelectControl/SelectControl.tsx +172 -0
  37. package/src/components/Controls/SelectControl/index.ts +1 -0
  38. package/src/components/Controls/SelectControl/styles.module.scss +5 -0
  39. package/src/components/Controls/SliderControl/SliderControl.tsx +92 -0
  40. package/src/components/Controls/SliderControl/index.ts +1 -0
  41. package/src/components/Controls/StepperControl/StepperControl.tsx +160 -0
  42. package/src/components/Controls/StepperControl/index.ts +1 -0
  43. package/src/components/Controls/StepperControl/styles.module.scss +16 -0
  44. package/src/components/Controls/TableControl/TableControl.tsx +162 -0
  45. package/src/components/Controls/TableControl/helperComponents/StepperWithAllowedValues.tsx +50 -0
  46. package/src/components/Controls/TableControl/helperComponents/index.ts +1 -0
  47. package/src/components/Controls/TableControl/helperComponents/styles.module.scss +53 -0
  48. package/src/components/Controls/TableControl/index.ts +1 -0
  49. package/src/components/Controls/TableControl/styles.module.scss +104 -0
  50. package/src/components/Controls/ToggleCard/ToggleCard.tsx +46 -0
  51. package/src/components/Controls/ToggleCard/index.ts +1 -0
  52. package/src/components/Controls/ToggleCard/styles.module.scss +11 -0
  53. package/src/components/Controls/ToggleCardsControl/ToggleCardsControl.tsx +93 -0
  54. package/src/components/Controls/ToggleCardsControl/index.ts +1 -0
  55. package/src/components/Controls/ToggleCardsControl/styles.module.scss +40 -0
  56. package/src/components/Controls/ToggleControl/ToggleControl.tsx +59 -0
  57. package/src/components/Controls/ToggleControl/components/SwitchRow.tsx +98 -0
  58. package/src/components/Controls/ToggleControl/components/index.ts +1 -0
  59. package/src/components/Controls/ToggleControl/components/styles.module.scss +110 -0
  60. package/src/components/Controls/ToggleControl/index.ts +1 -0
  61. package/src/components/Controls/ToggleObjectControl/ToggleObjectControl.tsx +111 -0
  62. package/src/components/Controls/ToggleObjectControl/index.ts +1 -0
  63. package/src/components/Controls/ToggleObjectControl/styles.module.scss +13 -0
  64. package/src/components/Controls/constants.ts +16 -0
  65. package/src/components/Controls/index.tsx +26 -0
  66. package/src/components/Controls/types.ts +181 -0
  67. package/src/components/Controls/utils.ts +49 -0
  68. package/src/components/Disclaimer/Disclaimer.tsx +33 -0
  69. package/src/components/Disclaimer/index.ts +1 -0
  70. package/src/components/Disclaimer/styles.module.scss +14 -0
  71. package/src/components/HeaderContainer/index.tsx +32 -0
  72. package/src/components/HeaderContainer/styles.module.scss +24 -0
  73. package/src/components/PriceSummary/PriceSummary.tsx +158 -0
  74. package/src/components/PriceSummary/components/PricePeriodSelect/PricePeriodSelect.tsx +81 -0
  75. package/src/components/PriceSummary/components/PricePeriodSelect/index.ts +1 -0
  76. package/src/components/PriceSummary/components/ProductCard/ProductCard.tsx +85 -0
  77. package/src/components/PriceSummary/components/ProductCard/index.ts +1 -0
  78. package/src/components/PriceSummary/components/ProductCard/styles.module.scss +7 -0
  79. package/src/components/PriceSummary/components/ProductListActions/ProductListActions.tsx +118 -0
  80. package/src/components/PriceSummary/components/ProductListActions/index.ts +1 -0
  81. package/src/components/PriceSummary/components/ProductListActions/styles.module.scss +38 -0
  82. package/src/components/PriceSummary/components/index.ts +3 -0
  83. package/src/components/PriceSummary/hooks.ts +36 -0
  84. package/src/components/PriceSummary/index.ts +1 -0
  85. package/src/components/PriceSummary/styles.module.scss +110 -0
  86. package/src/components/PriceSummary/utils/getTotalPrice.ts +19 -0
  87. package/src/components/PriceSummary/utils/index.ts +2 -0
  88. package/src/components/PriceSummary/utils/transformToProductCardData.ts +36 -0
  89. package/src/components/ProductHeadline/ProductPageHeadline.tsx +134 -0
  90. package/src/components/ProductHeadline/components/PriceCount.tsx +93 -0
  91. package/src/components/ProductHeadline/components/index.ts +1 -0
  92. package/src/components/ProductHeadline/components/styles.module.scss +52 -0
  93. package/src/components/ProductHeadline/index.ts +1 -0
  94. package/src/components/ProductHeadline/styles.module.scss +32 -0
  95. package/src/components/ProductPage/ProductPage.tsx +105 -0
  96. package/src/components/ProductPage/index.ts +1 -0
  97. package/src/components/ProductPage/styles.module.scss +13 -0
  98. package/src/components/Welcome/Welcome.tsx +138 -0
  99. package/src/components/Welcome/index.ts +1 -0
  100. package/src/components/Welcome/styles.module.scss +155 -0
  101. package/src/components/index.ts +3 -0
  102. package/src/config/config.tsx +27 -0
  103. package/src/config/index.ts +2 -0
  104. package/src/config/platforms/advanced/catalog.ts +82 -0
  105. package/src/config/platforms/advanced/constants.ts +23 -0
  106. package/src/config/platforms/advanced/index.ts +5 -0
  107. package/src/config/platforms/advanced/platform.tsx +13 -0
  108. package/src/config/platforms/advanced/product-config/AdvancedCloudBackup.ts +76 -0
  109. package/src/config/platforms/advanced/product-config/AdvancedCloudContainerEngine.tsx +260 -0
  110. package/src/config/platforms/advanced/product-config/AdvancedCloudServer.tsx +526 -0
  111. package/src/config/platforms/advanced/product-config/AdvancedDataAsYouUse.ts +159 -0
  112. package/src/config/platforms/advanced/product-config/AdvancedDataLakeInsight.ts +134 -0
  113. package/src/config/platforms/advanced/product-config/AdvancedDataWarehouseService.ts +94 -0
  114. package/src/config/platforms/advanced/product-config/AdvancedDcsMemcached.ts +69 -0
  115. package/src/config/platforms/advanced/product-config/AdvancedDcsRedis.ts +220 -0
  116. package/src/config/platforms/advanced/product-config/AdvancedDmsKafka.tsx +186 -0
  117. package/src/config/platforms/advanced/product-config/AdvancedDmsRabbitMq.ts +202 -0
  118. package/src/config/platforms/advanced/product-config/AdvancedDocumentDatabase.tsx +323 -0
  119. package/src/config/platforms/advanced/product-config/AdvancedElasticLoadBalance.tsx +252 -0
  120. package/src/config/platforms/advanced/product-config/AdvancedElasticSearch.ts +365 -0
  121. package/src/config/platforms/advanced/product-config/AdvancedFunctionGraph.tsx +65 -0
  122. package/src/config/platforms/advanced/product-config/AdvancedMapReduce.tsx +394 -0
  123. package/src/config/platforms/advanced/product-config/AdvancedMySqlDataBase.ts +417 -0
  124. package/src/config/platforms/advanced/product-config/AdvancedNetwork.ts +146 -0
  125. package/src/config/platforms/advanced/product-config/AdvancedObjectStorage.ts +232 -0
  126. package/src/config/platforms/advanced/product-config/AdvancedPostgreSqlDatabase.ts +402 -0
  127. package/src/config/platforms/advanced/product-config/AdvancedScalableFile.tsx +161 -0
  128. package/src/config/platforms/advanced/product-config/AdvancedSqlDatabase.ts +380 -0
  129. package/src/config/platforms/advanced/product-config/index.ts +21 -0
  130. package/src/config/platforms/advanced/products.ts +267 -0
  131. package/src/config/platforms/evolution/catalog.tsx +107 -0
  132. package/src/config/platforms/evolution/constants.ts +80 -0
  133. package/src/config/platforms/evolution/index.ts +5 -0
  134. package/src/config/platforms/evolution/platform.tsx +13 -0
  135. package/src/config/platforms/evolution/product-config/EvolutionArenadataDb.ts +151 -0
  136. package/src/config/platforms/evolution/product-config/EvolutionArtifactRegistry.ts +57 -0
  137. package/src/config/platforms/evolution/product-config/EvolutionBareMetal.tsx +172 -0
  138. package/src/config/platforms/evolution/product-config/EvolutionCloudServer.tsx +407 -0
  139. package/src/config/platforms/evolution/product-config/EvolutionCloudServerFreeTier.tsx +185 -0
  140. package/src/config/platforms/evolution/product-config/EvolutionCloudServerGpu.tsx +301 -0
  141. package/src/config/platforms/evolution/product-config/EvolutionContainerApps.ts +84 -0
  142. package/src/config/platforms/evolution/product-config/EvolutionContainerAppsFreeTier.ts +29 -0
  143. package/src/config/platforms/evolution/product-config/EvolutionKubernetes.ts +388 -0
  144. package/src/config/platforms/evolution/product-config/EvolutionManagedMetastore.ts +38 -0
  145. package/src/config/platforms/evolution/product-config/EvolutionManagedRedis.ts +161 -0
  146. package/src/config/platforms/evolution/product-config/EvolutionManagedSpark.ts +360 -0
  147. package/src/config/platforms/evolution/product-config/EvolutionManagedTrino.ts +53 -0
  148. package/src/config/platforms/evolution/product-config/EvolutionMlInference.tsx +703 -0
  149. package/src/config/platforms/evolution/product-config/EvolutionPostgreSql.ts +217 -0
  150. package/src/config/platforms/evolution/product-config/EvolutionPublicIp.ts +39 -0
  151. package/src/config/platforms/evolution/product-config/EvolutionSnatGateway.ts +22 -0
  152. package/src/config/platforms/evolution/product-config/EvolutionStorageS3.ts +223 -0
  153. package/src/config/platforms/evolution/product-config/EvolutionStorageS3FreeTier.ts +97 -0
  154. package/src/config/platforms/evolution/product-config/index.ts +17 -0
  155. package/src/config/platforms/evolution/products.ts +260 -0
  156. package/src/config/platforms/index.ts +3 -0
  157. package/src/config/platforms/vmware/catalog.ts +19 -0
  158. package/src/config/platforms/vmware/constants.ts +6 -0
  159. package/src/config/platforms/vmware/index.ts +5 -0
  160. package/src/config/platforms/vmware/platform.tsx +13 -0
  161. package/src/config/platforms/vmware/product-config/VmWareCloudBackup.ts +56 -0
  162. package/src/config/platforms/vmware/product-config/VmWareVirtualDataCenter.ts +309 -0
  163. package/src/config/platforms/vmware/product-config/VmWareVirtualMachinesBackup.ts +58 -0
  164. package/src/config/platforms/vmware/product-config/VmWareVirtualWorkspaces.tsx +210 -0
  165. package/src/config/platforms/vmware/product-config/index.ts +4 -0
  166. package/src/config/platforms/vmware/products.ts +63 -0
  167. package/src/config/styles.module.scss +9 -0
  168. package/src/config/utils/disk.ts +94 -0
  169. package/src/config/utils/diskPostgreSqlMySQL.ts +94 -0
  170. package/src/config/utils/eip.ts +104 -0
  171. package/src/config/utils/index.ts +52 -0
  172. package/src/config/utils/obs.ts +100 -0
  173. package/src/constants.ts +56 -0
  174. package/src/contexts/CalculatorContext.tsx +193 -0
  175. package/src/contexts/ProductContext.ts +25 -0
  176. package/src/contexts/index.ts +2 -0
  177. package/src/hooks/index.ts +6 -0
  178. package/src/hooks/useAdaptive.ts +20 -0
  179. package/src/hooks/useCatalogCardClick.ts +37 -0
  180. package/src/hooks/useProductClick.ts +14 -0
  181. package/src/hooks/useProductDelete.ts +46 -0
  182. package/src/index.ts +6 -0
  183. package/src/services/constants.ts +5 -0
  184. package/src/services/getFetcherFn.ts +57 -0
  185. package/src/services/getInitPrice.ts +64 -0
  186. package/src/services/getOnDownloadFileClick.ts +37 -0
  187. package/src/services/getOnShareClick.ts +56 -0
  188. package/src/services/index.ts +4 -0
  189. package/src/types/CalculatorType.ts +16 -0
  190. package/src/types/CatalogConfig.ts +13 -0
  191. package/src/types/Category.ts +14 -0
  192. package/src/types/FetcherFn.ts +26 -0
  193. package/src/types/FormValues.ts +4 -0
  194. package/src/types/Platform.ts +24 -0
  195. package/src/types/Price.ts +18 -0
  196. package/src/types/Product.ts +39 -0
  197. package/src/types/ProductState.ts +11 -0
  198. package/src/types/State.ts +7 -0
  199. package/src/types/index.ts +10 -0
  200. package/src/utils/filterNonEmptyArrays.ts +13 -0
  201. package/src/utils/formatNumber.ts +5 -0
  202. package/src/utils/getPrice.ts +38 -0
  203. package/src/utils/index.ts +5 -0
  204. package/src/utils/parseKeyToDataTest.ts +14 -0
  205. package/src/utils/value.ts +11 -0
@@ -0,0 +1,110 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as stv;
2
+
3
+ .scroll {
4
+ min-height: 320px;
5
+ height: 100%;
6
+ padding: stv.$dimension-3m;
7
+ }
8
+
9
+ .body {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: stv.$dimension-1m;
13
+ }
14
+
15
+ .content {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: stv.$dimension-3m;
19
+ }
20
+
21
+ .headline {
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ gap: stv.$dimension-2m;
26
+ padding: 0 stv.$dimension-4m;
27
+ height: 104px;
28
+ box-sizing: border-box;
29
+ border-bottom: solid 1px stv.$sys-neutral-decor-default;
30
+
31
+ &[data-mobile] {
32
+ height: 76px;
33
+ padding: 0 stv.$dimension-3m;
34
+
35
+ @media (max-width: 440px) {
36
+ height: 60px;
37
+ padding: 0 stv.$dimension-2m;
38
+ }
39
+ }
40
+ }
41
+
42
+ .footer {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: stv.$dimension-2m;
46
+ padding: stv.$dimension-3m;
47
+ box-sizing: border-box;
48
+ border-top: solid 1px stv.$sys-neutral-decor-default;
49
+ }
50
+
51
+ .caption {
52
+ @include stv.composite-var(stv.$sans-body-s);
53
+
54
+ color: stv.$sys-neutral-text-light;
55
+ }
56
+
57
+ .priceSummary {
58
+ background-color: stv.$sys-neutral-background1-level;
59
+
60
+ display: flex;
61
+
62
+ flex-direction: column;
63
+
64
+ flex-shrink: 0;
65
+
66
+ &[data-mobile] {
67
+ .headline {
68
+ flex-direction: column;
69
+
70
+ height: auto;
71
+
72
+ align-items: inherit;
73
+ justify-content: inherit;
74
+ }
75
+
76
+ .headline,
77
+ .scroll,
78
+ .footer {
79
+ padding: stv.$dimension-2m;
80
+ }
81
+ }
82
+ }
83
+
84
+ .total {
85
+ color: stv.$sys-neutral-text-main;
86
+
87
+ display: flex;
88
+ gap: stv.$dimension-1m;
89
+
90
+ justify-content: flex-start;
91
+ align-items: center;
92
+
93
+ overflow: hidden;
94
+
95
+ * > {
96
+ min-width: 0;
97
+ }
98
+ }
99
+
100
+ .platform {
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: stv.$dimension-1m;
104
+ }
105
+
106
+ .footerHeadline {
107
+ display: flex;
108
+ gap: stv.$dimension-1m;
109
+ align-items: center;
110
+ }
@@ -0,0 +1,19 @@
1
+ import { DEFAULT_PRICE } from '../../../constants';
2
+ import { Price } from '../../../types';
3
+
4
+ export function getTotalPrice(price: Record<string, { price: Price }[]>) {
5
+ const total = { ...DEFAULT_PRICE };
6
+
7
+ Object.values(price).forEach((productPriceArr: { price: Price }[]) => {
8
+ productPriceArr.forEach(({ price: priceElem = {} as Price }) => {
9
+ total.priceMonthNds = total.priceMonthNds + priceElem.priceMonthNds;
10
+ total.priceDayNds = total.priceDayNds + priceElem.priceDayNds;
11
+ total.priceHourNds = total.priceHourNds + priceElem.priceHourNds;
12
+ total.pricePartnersDayNds = total.pricePartnersDayNds + priceElem.pricePartnersDayNds;
13
+ total.pricePartnersHourNds = total.pricePartnersHourNds + priceElem.pricePartnersHourNds;
14
+ total.pricePartnersMonthNds = total.pricePartnersMonthNds + priceElem.pricePartnersMonthNds;
15
+ });
16
+ });
17
+
18
+ return total;
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './getTotalPrice';
2
+ export * from './transformToProductCardData';
@@ -0,0 +1,36 @@
1
+ import { PLATFORM } from '../../../constants';
2
+ import { ProductsState } from '../../../contexts';
3
+ import { FormValues, PlatformType, Product as ProductConfig, ProductState } from '../../../types';
4
+
5
+ export type ProductCardType = {
6
+ label: string;
7
+ productQuantity: number;
8
+ id: string;
9
+ idx: number;
10
+ formData: FormValues;
11
+ };
12
+
13
+ export function transformValueToProductCardData(products: Record<string, ProductConfig>, value: ProductsState) {
14
+ return Object.keys(value).reduce(
15
+ (res, productId) => {
16
+ const cards: ProductCardType[] = value[productId].map(({ data }: ProductState, idx: number) => ({
17
+ idx,
18
+ id: productId,
19
+ label: products[productId].label + (value[productId].length > 1 ? ` ${idx + 1}` : ''),
20
+ productQuantity: data?.productQuantity,
21
+ formData: data,
22
+ }));
23
+
24
+ res[products[productId].platform] = res[products[productId].platform].concat(cards);
25
+
26
+ return res;
27
+ },
28
+ {
29
+ [PLATFORM.Evolution]: [],
30
+ [PLATFORM.Advanced]: [],
31
+ [PLATFORM.VmWare]: [],
32
+ [PLATFORM.MlSpace]: [],
33
+ [PLATFORM.Test]: [],
34
+ } as Record<PlatformType, ProductCardType[]>,
35
+ );
36
+ }
@@ -0,0 +1,134 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ import { LAYOUT_TYPE } from '@cloud-ru/uikit-product-utils';
4
+ import { ButtonTonal } from '@snack-uikit/button';
5
+ import { FieldStepper } from '@snack-uikit/fields';
6
+ import { IconPredefined } from '@snack-uikit/icon-predefined';
7
+ import { Tooltip } from '@snack-uikit/tooltip';
8
+ import { TruncateString } from '@snack-uikit/truncate-string';
9
+ import { Typography } from '@snack-uikit/typography';
10
+
11
+ import { useCalculatorContext, useProductContext } from '../../contexts';
12
+ import { AnyType, CALCULATOR_TYPE, Product } from '../../types';
13
+ import { getValue, parseKeyToDataTest, setValue } from '../../utils';
14
+ import { HeaderContainer } from '../HeaderContainer';
15
+ import { PriceCount } from './components';
16
+ import styles from './styles.module.scss';
17
+
18
+ type ProductPageHeadlineProps = {
19
+ product: Product;
20
+ };
21
+
22
+ export function ProductPageHeadline({ product }: ProductPageHeadlineProps) {
23
+ const { icon, label, enableChangeProductQuantity, productQuantityValues, freeTier, disabledProductQuantity } =
24
+ product;
25
+ const { min: minProductQuantity = 1, max: maxProductQuantity = 99 } = productQuantityValues ?? {};
26
+
27
+ const { value: valueProp, onChange: onChangeProp, price } = useProductContext();
28
+
29
+ const value = getValue(valueProp, 'productQuantity');
30
+
31
+ const {
32
+ layoutType,
33
+ pricePeriod,
34
+ calculatorType,
35
+ actions: { onConnectClick },
36
+ onAnalyticsClick,
37
+ selectedProduct,
38
+ } = useCalculatorContext();
39
+
40
+ const onChange = (newValue: AnyType) => {
41
+ setValue(valueProp, 'productQuantity', newValue);
42
+ onAnalyticsClick(String(newValue), `productQuantity`);
43
+ onChangeProp(valueProp);
44
+ };
45
+
46
+ const isPartners = calculatorType === CALCULATOR_TYPE.Partners;
47
+ const isProductType = calculatorType === CALCULATOR_TYPE.Product;
48
+ const isMobile = layoutType !== LAYOUT_TYPE.Desktop && layoutType !== LAYOUT_TYPE.DesktopSmall;
49
+ const hasCounter = enableChangeProductQuantity || freeTier;
50
+ const TitleComponent = isMobile ? Typography.SansTitleM : Typography.SansTitleL;
51
+ const headerRef = useRef<HTMLDivElement>(null);
52
+
53
+ useEffect(() => {
54
+ if (headerRef.current && isMobile && !isProductType) {
55
+ headerRef.current.scrollIntoView({ behavior: 'smooth' });
56
+ }
57
+ }, [selectedProduct, isMobile, isProductType]);
58
+
59
+ function ConnectButton() {
60
+ const dataTestAttribute = parseKeyToDataTest('product', 'connect-button');
61
+
62
+ return !isPartners && product.enableConnectToConsole ? (
63
+ <Tooltip
64
+ hoverDelayOpen={600}
65
+ tip='Вы будете перенаправлены в личный кабинет для подключения выбранной конфигурации'
66
+ >
67
+ <ButtonTonal
68
+ fullWidth={isMobile}
69
+ label='Подключить'
70
+ size='m'
71
+ onClick={() => onConnectClick?.(product.id, valueProp)}
72
+ data-test-id={dataTestAttribute}
73
+ />
74
+ </Tooltip>
75
+ ) : null;
76
+ }
77
+
78
+ return (
79
+ <HeaderContainer ref={headerRef}>
80
+ <div className={styles.left}>
81
+ <IconPredefined
82
+ icon={icon}
83
+ size={isMobile ? 's' : 'm'}
84
+ decor={false}
85
+ appearance='primary'
86
+ data-test-id={parseKeyToDataTest('product', 'icon')}
87
+ />
88
+
89
+ <TitleComponent>
90
+ <TruncateString
91
+ data-test-id={parseKeyToDataTest('product', 'title')}
92
+ variant='end'
93
+ text={label}
94
+ maxLines={2}
95
+ />
96
+ </TitleComponent>
97
+ </div>
98
+
99
+ <div className={styles.right} data-mobile={isMobile || undefined}>
100
+ <PriceCount
101
+ price={price}
102
+ pricePeriod={pricePeriod}
103
+ freeTier={freeTier}
104
+ mobile={isMobile}
105
+ partners={isPartners}
106
+ hasCounter={hasCounter}
107
+ />
108
+
109
+ {hasCounter && (
110
+ <div
111
+ className={styles.counter}
112
+ data-mobile={isMobile || undefined}
113
+ data-test-id={parseKeyToDataTest('product', 'counter')}
114
+ >
115
+ <FieldStepper
116
+ size='m'
117
+ step={1}
118
+ min={minProductQuantity}
119
+ max={maxProductQuantity}
120
+ value={value}
121
+ onChange={onChange}
122
+ disabled={freeTier || disabledProductQuantity}
123
+ allowMoreThanLimits={false}
124
+ />
125
+ </div>
126
+ )}
127
+
128
+ {!isMobile && <ConnectButton />}
129
+ </div>
130
+
131
+ {isMobile && <ConnectButton />}
132
+ </HeaderContainer>
133
+ );
134
+ }
@@ -0,0 +1,93 @@
1
+ import { Divider } from '@snack-uikit/divider';
2
+ import { TruncateString } from '@snack-uikit/truncate-string';
3
+ import { Typography } from '@snack-uikit/typography';
4
+
5
+ import { Price, PRICE_PERIOD, PricePeriod } from '../../../types';
6
+ import { formatNumber, getPrice, parseKeyToDataTest } from '../../../utils';
7
+ import styles from './styles.module.scss';
8
+
9
+ const PRICE_NAME: Record<PricePeriod, string> = {
10
+ [PRICE_PERIOD.Month]: 'В месяц',
11
+ [PRICE_PERIOD.Day]: 'В день',
12
+ [PRICE_PERIOD.Hour]: 'В час',
13
+ };
14
+
15
+ type PriceCountProps = {
16
+ price?: Price;
17
+ pricePeriod: PricePeriod;
18
+ partners?: boolean;
19
+ freeTier?: boolean;
20
+ mobile?: boolean;
21
+ hasCounter?: boolean;
22
+ };
23
+
24
+ export function BasePriceCount({ price, pricePeriod, freeTier, mobile }: PriceCountProps) {
25
+ const total = getPrice({ price, pricePeriod });
26
+ const dataTestAttribute = parseKeyToDataTest('product', 'price');
27
+
28
+ if (freeTier && !total)
29
+ return (
30
+ <div className={styles.price} data-mobile={mobile || undefined}>
31
+ Бесплатно
32
+ <br />
33
+ навсегда
34
+ </div>
35
+ );
36
+
37
+ return (
38
+ <div className={styles.price} data-mobile={mobile || undefined} data-test-id={dataTestAttribute}>
39
+ <div className={styles.total}>
40
+ <Typography.SansTitleM>
41
+ <TruncateString text={formatNumber(total)} maxLines={1} />
42
+ </Typography.SansTitleM>
43
+ <Typography.SansBodyL>₽</Typography.SansBodyL>
44
+ </div>
45
+ <div className={styles.caption}>{`${PRICE_NAME[pricePeriod]} c НДС`}</div>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ export function PartnersPriceCount({ price, pricePeriod, mobile, hasCounter }: PriceCountProps) {
51
+ const total = getPrice({ price, pricePeriod });
52
+ const partnersTotal = getPrice({ price, pricePeriod, partners: true });
53
+ const mobileWithCounter = mobile && hasCounter;
54
+
55
+ const dataTestAttribute = parseKeyToDataTest('product', 'price-partners');
56
+ const partnersSale = `-${100 - Math.trunc((partnersTotal / total) * 100 + 0.1)}%`;
57
+
58
+ return (
59
+ <div
60
+ className={styles.partnersWrapper}
61
+ data-mobile-with-counter={mobileWithCounter || undefined}
62
+ data-test-id={dataTestAttribute}
63
+ >
64
+ <div className={styles.price} data-mobile={mobile || undefined}>
65
+ <div className={styles.caption}>Рыночная цена</div>
66
+ <div className={styles.total}>
67
+ <Typography.SansTitleM>
68
+ <TruncateString text={formatNumber(total)} maxLines={1} />
69
+ </Typography.SansTitleM>
70
+ <Typography.SansBodyL>₽</Typography.SansBodyL>
71
+ </div>
72
+ </div>
73
+
74
+ <Divider orientation={mobileWithCounter ? 'horizontal' : 'vertical'} className={styles.divider} />
75
+
76
+ <div className={styles.price} data-mobile={mobile || undefined}>
77
+ <div className={styles.caption}>
78
+ Партнерская цена {total > 0 && <span className={styles.sale}>{partnersSale}</span>}
79
+ </div>
80
+ <div className={styles.total}>
81
+ <Typography.SansTitleM>
82
+ <TruncateString text={formatNumber(partnersTotal)} maxLines={1} />
83
+ </Typography.SansTitleM>
84
+ <Typography.SansBodyL>₽</Typography.SansBodyL>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ );
89
+ }
90
+
91
+ export function PriceCount({ partners, ...props }: PriceCountProps) {
92
+ return partners ? <PartnersPriceCount {...props} /> : <BasePriceCount {...props} />;
93
+ }
@@ -0,0 +1 @@
1
+ export * from './PriceCount';
@@ -0,0 +1,52 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as stv;
2
+
3
+ .price {
4
+ display: flex;
5
+ justify-content: space-between;
6
+ flex-direction: column;
7
+ text-align: end;
8
+ width: 160px;
9
+
10
+ .total {
11
+ color: stv.$sys-neutral-text-main;
12
+
13
+ text-overflow: ellipsis;
14
+ white-space: nowrap;
15
+
16
+ display: flex;
17
+ gap: stv.$dimension-050m;
18
+
19
+ justify-content: flex-end;
20
+ align-items: center;
21
+ }
22
+
23
+ .caption {
24
+ @include stv.composite-var(stv.$sans-body-s);
25
+ color: stv.$sys-neutral-text-support;
26
+ }
27
+
28
+ &[data-mobile],
29
+ &[data-mobile] > .total {
30
+ justify-content: flex-start;
31
+ text-align: left;
32
+ }
33
+ }
34
+
35
+ .sale {
36
+ color: stv.$sys-red-text-support;
37
+ }
38
+
39
+ .partnersWrapper {
40
+ display: grid;
41
+ grid-template-columns: repeat(3, auto);
42
+ box-sizing: border-box;
43
+
44
+ gap: stv.$dimension-1m;
45
+
46
+ &[data-mobile-with-counter] {
47
+ flex: 1;
48
+ grid-template-columns: 1fr;
49
+ padding-right: stv.$dimension-1m;
50
+ border-right: solid 1px stv.$sys-neutral-decor-default;
51
+ }
52
+ }
@@ -0,0 +1 @@
1
+ export * from './ProductPageHeadline';
@@ -0,0 +1,32 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as stv;
2
+
3
+ .left,
4
+ .right {
5
+ display: flex;
6
+ align-items: center;
7
+ flex-direction: row;
8
+ }
9
+
10
+ .left {
11
+ gap: stv.$dimension-1m;
12
+ }
13
+
14
+ .right {
15
+ justify-content: flex-end;
16
+ align-items: flex-start;
17
+ gap: stv.$dimension-2m;
18
+
19
+ &[data-mobile] {
20
+ justify-content: space-between;
21
+ flex-wrap: wrap;
22
+ }
23
+ }
24
+
25
+ .counter {
26
+ max-width: 140px;
27
+ min-width: 100px;
28
+
29
+ &[data-mobile] {
30
+ width: 100px;
31
+ }
32
+ }
@@ -0,0 +1,105 @@
1
+ import deepmerge from 'deepmerge';
2
+ import { useDeferredValue, useEffect } from 'react';
3
+ import useSWR from 'swr';
4
+
5
+ import { useValueControl } from '@snack-uikit/utils';
6
+
7
+ import { ProductContext, ProductsState, useCalculatorContext } from '../../contexts';
8
+ import { useAdaptive } from '../../hooks';
9
+ import { AnyType } from '../../types';
10
+ import { getValue, parseKeyToDataTest, setValue } from '../../utils';
11
+ import { CONTROL } from '../Controls';
12
+ import { Control } from '../Controls/Control';
13
+ import { ProductPageHeadline } from '../ProductHeadline';
14
+ import styles from './styles.module.scss';
15
+
16
+ type ProductPageProps = {
17
+ selectedProduct: { id: string; idx: number };
18
+
19
+ className?: string;
20
+ };
21
+
22
+ export function ProductPage({ className, selectedProduct }: ProductPageProps) {
23
+ const { calculatorType, config, products, setProducts, fetcherFn } = useCalculatorContext();
24
+
25
+ const { isMobile } = useAdaptive();
26
+
27
+ const baseAccessorKey = `${selectedProduct.id}[${selectedProduct.idx}]`;
28
+
29
+ const accessorKey = {
30
+ data: `${baseAccessorKey}.data`,
31
+ price: `${baseAccessorKey}.price`,
32
+ priceList: `${baseAccessorKey}.priceList`,
33
+ };
34
+
35
+ const [formData, setFormData] = useValueControl({
36
+ value: getValue(products, accessorKey.data),
37
+ onChange: newFormData => {
38
+ setProducts((value: AnyType) => {
39
+ setValue(value, accessorKey.data, newFormData);
40
+
41
+ return value;
42
+ });
43
+ },
44
+ });
45
+
46
+ const deferredFormData = useDeferredValue(JSON.stringify(formData));
47
+ const deferredSelectedProductId = useDeferredValue(selectedProduct.id);
48
+
49
+ const { data } = useSWR(
50
+ {
51
+ productId: deferredSelectedProductId,
52
+ formData: deferredFormData,
53
+ calculatorType: calculatorType,
54
+ },
55
+ fetcherFn,
56
+ );
57
+
58
+ useEffect(() => {
59
+ if (data) {
60
+ setProducts((oldValue: ProductsState) => {
61
+ data?.price && setValue(oldValue, accessorKey.price, data.price);
62
+
63
+ if (data?.values) {
64
+ const oldProductData = getValue(oldValue, accessorKey.data);
65
+
66
+ setValue(oldValue, accessorKey.data, deepmerge(oldProductData, data.values));
67
+ }
68
+
69
+ data?.priceList && setValue(oldValue, accessorKey.priceList, data.priceList);
70
+
71
+ return { ...oldValue };
72
+ });
73
+ }
74
+ }, [data, setProducts, accessorKey.price, accessorKey.priceList, accessorKey.data]);
75
+
76
+ const deferredPrice = useDeferredValue(getValue(products, accessorKey.price));
77
+ const deferredPriceList = useDeferredValue(getValue(products, accessorKey.priceList));
78
+ const dataTestAttribute = parseKeyToDataTest('configurator');
79
+
80
+ return (
81
+ <ProductContext.Provider
82
+ value={{
83
+ value: { ...getValue(products, accessorKey.data) },
84
+ onChange: setFormData,
85
+
86
+ price: deferredPrice,
87
+ priceList: deferredPriceList,
88
+ }}
89
+ >
90
+ <div className={className} data-test-id={dataTestAttribute}>
91
+ <ProductPageHeadline product={config.products[selectedProduct.id]} />
92
+
93
+ <div className={styles.content} data-mobile={isMobile || undefined}>
94
+ <Control
95
+ formControl={{
96
+ type: CONTROL.Object,
97
+ ui: config.products[selectedProduct.id].formConfig?.ui ?? [],
98
+ controls: config.products[selectedProduct.id].formConfig?.controls ?? {},
99
+ }}
100
+ />
101
+ </div>
102
+ </div>
103
+ </ProductContext.Provider>
104
+ );
105
+ }
@@ -0,0 +1 @@
1
+ export * from './ProductPage';
@@ -0,0 +1,13 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as stv;
2
+
3
+ .content {
4
+ padding: stv.$dimension-3m;
5
+ display: flex;
6
+ gap: stv.$dimension-3m;
7
+ flex-direction: column;
8
+
9
+ &[data-mobile] {
10
+ padding: stv.$dimension-2m;
11
+ gap: stv.$dimension-2m;
12
+ }
13
+ }