@alfalab/core-components-confirmation 12.5.3 → 13.1.0

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 (181) hide show
  1. package/component.desktop.d.ts +1 -1
  2. package/component.desktop.js +6 -4
  3. package/component.mobile.js +7 -5
  4. package/component.responsive.d.ts +2 -3
  5. package/component.responsive.js +10 -10
  6. package/components/base-confirmation/component.js +11 -7
  7. package/components/base-confirmation/index.css +2 -2
  8. package/components/base-confirmation/index.js +6 -4
  9. package/components/countdown-loader/component.js +1 -1
  10. package/components/countdown-loader/index.css +2 -2
  11. package/components/header/component.js +1 -1
  12. package/components/header/index.css +4 -4
  13. package/components/index.js +6 -4
  14. package/components/screens/fatal-error/component.js +3 -3
  15. package/components/screens/fatal-error/index.css +6 -6
  16. package/components/screens/hint/component.js +3 -3
  17. package/components/screens/hint/index.css +12 -12
  18. package/components/screens/index.js +6 -4
  19. package/components/screens/initial/component.js +23 -11
  20. package/components/screens/initial/countdown-section.js +2 -2
  21. package/components/screens/initial/index.css +17 -20
  22. package/components/screens/initial/index.js +6 -4
  23. package/components/screens/temp-block/component.js +1 -1
  24. package/components/screens/temp-block/index.css +7 -7
  25. package/context.js +2 -0
  26. package/{countdown-section-751424e6.js → countdown-section-b212c5b4.js} +5 -5
  27. package/cssm/component.desktop.d.ts +1 -1
  28. package/cssm/component.desktop.js +5 -3
  29. package/cssm/component.mobile.js +5 -3
  30. package/cssm/component.responsive.d.ts +2 -3
  31. package/cssm/component.responsive.js +9 -9
  32. package/cssm/components/base-confirmation/component.js +9 -5
  33. package/cssm/components/base-confirmation/index.js +5 -3
  34. package/cssm/components/index.js +5 -3
  35. package/cssm/components/screens/fatal-error/component.js +2 -2
  36. package/cssm/components/screens/hint/component.js +2 -2
  37. package/cssm/components/screens/index.js +5 -3
  38. package/cssm/components/screens/initial/component.js +22 -10
  39. package/cssm/components/screens/initial/countdown-section.js +4 -4
  40. package/cssm/components/screens/initial/index.js +5 -3
  41. package/cssm/components/screens/initial/index.module.css +0 -3
  42. package/cssm/context.js +2 -0
  43. package/cssm/desktop.d.ts +1 -1
  44. package/cssm/desktop.js +8 -4
  45. package/cssm/index.d.ts +2 -6
  46. package/cssm/index.js +19 -27
  47. package/cssm/mobile.d.ts +1 -1
  48. package/cssm/mobile.js +8 -4
  49. package/cssm/shared.d.ts +4 -0
  50. package/cssm/shared.js +43 -0
  51. package/cssm/types.d.ts +16 -1
  52. package/desktop.d.ts +1 -1
  53. package/desktop.js +9 -5
  54. package/esm/component.desktop.d.ts +1 -1
  55. package/esm/component.desktop.js +6 -4
  56. package/esm/component.mobile.js +7 -5
  57. package/esm/component.responsive.d.ts +2 -3
  58. package/esm/component.responsive.js +10 -10
  59. package/esm/components/base-confirmation/component.js +11 -7
  60. package/esm/components/base-confirmation/index.css +2 -2
  61. package/esm/components/base-confirmation/index.js +6 -4
  62. package/esm/components/countdown-loader/component.js +1 -1
  63. package/esm/components/countdown-loader/index.css +2 -2
  64. package/esm/components/header/component.js +1 -1
  65. package/esm/components/header/index.css +4 -4
  66. package/esm/components/index.js +6 -4
  67. package/esm/components/screens/fatal-error/component.js +3 -3
  68. package/esm/components/screens/fatal-error/index.css +6 -6
  69. package/esm/components/screens/hint/component.js +3 -3
  70. package/esm/components/screens/hint/index.css +12 -12
  71. package/esm/components/screens/index.js +6 -4
  72. package/esm/components/screens/initial/component.js +19 -7
  73. package/esm/components/screens/initial/countdown-section.js +2 -2
  74. package/esm/components/screens/initial/index.css +17 -20
  75. package/esm/components/screens/initial/index.js +6 -4
  76. package/esm/components/screens/temp-block/component.js +1 -1
  77. package/esm/components/screens/temp-block/index.css +7 -7
  78. package/esm/context.js +2 -0
  79. package/esm/{countdown-section-c4ca26d0.js → countdown-section-83d922ef.js} +5 -5
  80. package/esm/desktop.d.ts +1 -1
  81. package/esm/desktop.js +7 -5
  82. package/esm/index.d.ts +2 -6
  83. package/esm/index.js +18 -15
  84. package/esm/mobile.css +2 -2
  85. package/esm/mobile.d.ts +1 -1
  86. package/esm/mobile.js +7 -5
  87. package/esm/shared.d.ts +4 -0
  88. package/esm/shared.js +20 -0
  89. package/esm/types.d.ts +16 -1
  90. package/index.d.ts +2 -6
  91. package/index.js +17 -25
  92. package/mobile.css +2 -2
  93. package/mobile.d.ts +1 -1
  94. package/mobile.js +9 -5
  95. package/modern/component.desktop.d.ts +1 -1
  96. package/modern/component.desktop.js +6 -4
  97. package/modern/component.mobile.js +7 -5
  98. package/modern/component.responsive.d.ts +2 -3
  99. package/modern/component.responsive.js +10 -10
  100. package/modern/components/base-confirmation/component.js +10 -6
  101. package/modern/components/base-confirmation/index.css +2 -2
  102. package/modern/components/base-confirmation/index.js +6 -4
  103. package/modern/components/countdown-loader/component.js +1 -1
  104. package/modern/components/countdown-loader/index.css +2 -2
  105. package/modern/components/header/component.js +1 -1
  106. package/modern/components/header/index.css +4 -4
  107. package/modern/components/index.js +6 -4
  108. package/modern/components/screens/fatal-error/component.js +3 -3
  109. package/modern/components/screens/fatal-error/index.css +6 -6
  110. package/modern/components/screens/hint/component.js +3 -3
  111. package/modern/components/screens/hint/index.css +12 -12
  112. package/modern/components/screens/index.js +6 -4
  113. package/modern/components/screens/initial/component.js +19 -7
  114. package/modern/components/screens/initial/countdown-section.js +2 -2
  115. package/modern/components/screens/initial/index.css +17 -20
  116. package/modern/components/screens/initial/index.js +6 -4
  117. package/modern/components/screens/temp-block/component.js +1 -1
  118. package/modern/components/screens/temp-block/index.css +7 -7
  119. package/modern/context.js +2 -0
  120. package/modern/{countdown-section-473f17f4.js → countdown-section-ea7af879.js} +5 -5
  121. package/modern/desktop.d.ts +1 -1
  122. package/modern/desktop.js +7 -5
  123. package/modern/index.d.ts +2 -6
  124. package/modern/index.js +18 -15
  125. package/modern/mobile.css +2 -2
  126. package/modern/mobile.d.ts +1 -1
  127. package/modern/mobile.js +7 -5
  128. package/modern/shared.d.ts +4 -0
  129. package/modern/shared.js +20 -0
  130. package/modern/types.d.ts +16 -1
  131. package/package.json +31 -6
  132. package/shared.d.ts +4 -0
  133. package/shared.js +37 -0
  134. package/src/component.desktop.tsx +10 -0
  135. package/src/component.mobile.tsx +13 -0
  136. package/src/component.responsive.tsx +31 -0
  137. package/src/components/base-confirmation/component.tsx +125 -0
  138. package/src/components/base-confirmation/index.module.css +8 -0
  139. package/src/components/base-confirmation/index.ts +1 -0
  140. package/src/components/countdown-loader/component.tsx +37 -0
  141. package/src/components/countdown-loader/index.module.css +7 -0
  142. package/src/components/countdown-loader/index.ts +1 -0
  143. package/src/components/header/component.tsx +28 -0
  144. package/src/components/header/index.module.css +21 -0
  145. package/src/components/header/index.ts +1 -0
  146. package/src/components/index.ts +2 -0
  147. package/src/components/screens/fatal-error/component.tsx +46 -0
  148. package/src/components/screens/fatal-error/index.module.css +23 -0
  149. package/src/components/screens/fatal-error/index.ts +1 -0
  150. package/src/components/screens/hint/component.tsx +94 -0
  151. package/src/components/screens/hint/index.module.css +51 -0
  152. package/src/components/screens/hint/index.ts +1 -0
  153. package/src/components/screens/index.ts +4 -0
  154. package/src/components/screens/initial/component.tsx +203 -0
  155. package/src/components/screens/initial/countdown-section.tsx +79 -0
  156. package/src/components/screens/initial/index.module.css +71 -0
  157. package/src/components/screens/initial/index.ts +1 -0
  158. package/src/components/screens/temp-block/component.tsx +58 -0
  159. package/src/components/screens/temp-block/index.module.css +28 -0
  160. package/src/components/screens/temp-block/index.ts +1 -0
  161. package/src/context.ts +27 -0
  162. package/src/desktop.ts +3 -0
  163. package/src/index.ts +6 -0
  164. package/src/mobile.module.css +3 -0
  165. package/src/mobile.ts +3 -0
  166. package/src/shared.ts +4 -0
  167. package/src/types.ts +228 -0
  168. package/src/utils.ts +107 -0
  169. package/src/vars.css +33 -0
  170. package/types.d.ts +16 -1
  171. package/cssm/responsive.d.ts +0 -2
  172. package/cssm/responsive.js +0 -39
  173. package/esm/responsive.d.ts +0 -2
  174. package/esm/responsive.js +0 -23
  175. package/modern/responsive.d.ts +0 -2
  176. package/modern/responsive.js +0 -22
  177. package/responsive.d.ts +0 -2
  178. package/responsive.js +0 -31
  179. /package/{countdown-section-751424e6.d.ts → countdown-section-b212c5b4.d.ts} +0 -0
  180. /package/esm/{countdown-section-c4ca26d0.d.ts → countdown-section-83d922ef.d.ts} +0 -0
  181. /package/modern/{countdown-section-473f17f4.d.ts → countdown-section-ea7af879.d.ts} +0 -0
@@ -0,0 +1,125 @@
1
+ import React, { ComponentType, FC, useEffect } from 'react';
2
+ import cn from 'classnames';
3
+
4
+ import { usePrevious } from '@alfalab/hooks';
5
+
6
+ import { ConfirmationContext } from '../../context';
7
+ import { ConfirmationProps, defaultTexts, TConfirmationContext } from '../../types';
8
+ import { ONE_DAY, ONE_MINUTE, useCountdown } from '../../utils';
9
+ import { FatalError, Hint, Initial, TempBlock } from '../screens';
10
+
11
+ import styles from './index.module.css';
12
+
13
+ const confirmationScreens: { [key: string]: ComponentType<{ mobile?: boolean }> } = {
14
+ INITIAL: Initial,
15
+ HINT: Hint,
16
+ FATAL_ERROR: FatalError,
17
+ TEMP_BLOCK: TempBlock,
18
+ };
19
+
20
+ export const BaseConfirmation: FC<ConfirmationProps> = ({
21
+ state,
22
+ screen,
23
+ alignContent = 'left',
24
+ children,
25
+ requiredCharAmount = 5,
26
+ countdownDuration = ONE_MINUTE,
27
+ tempBlockDuration = ONE_DAY,
28
+ phone,
29
+ blockSmsRetry,
30
+ dataTestId,
31
+ className,
32
+ getScreensMap,
33
+ onInputFinished,
34
+ onChangeState,
35
+ onSmsRetryClick,
36
+ onChangeScreen,
37
+ onFatalErrorOkButtonClick,
38
+ onTempBlockFinished,
39
+ mobile,
40
+ clearCodeOnError = true,
41
+ hideCountdownSection = false,
42
+ breakpoint = 1024,
43
+ initialScreenHintSlot,
44
+ ...restProps
45
+ }) => {
46
+ const [timeLeft, startTimer, stopTimer] = useCountdown(countdownDuration);
47
+
48
+ const prevState = usePrevious(state);
49
+
50
+ useEffect(() => {
51
+ startTimer();
52
+ }, [startTimer]);
53
+
54
+ useEffect(() => {
55
+ /**
56
+ * Перезапускаем таймер после повторного запроса кода
57
+ */
58
+ if (state === 'INITIAL' && prevState === 'CODE_SENDING') {
59
+ startTimer();
60
+ }
61
+ }, [state, prevState, startTimer]);
62
+
63
+ useEffect(() => {
64
+ /**
65
+ * Останавливаем таймер, если новый экран/состояние не содержит таймер
66
+ */
67
+ if (!['INITIAL', 'HINT', 'TEMP_BLOCK'].includes(screen) || blockSmsRetry) {
68
+ stopTimer();
69
+ }
70
+ }, [state, screen, blockSmsRetry, stopTimer]);
71
+
72
+ const handleSmsRetry = () => {
73
+ onChangeState('CODE_SENDING');
74
+
75
+ onSmsRetryClick();
76
+ };
77
+
78
+ const handleInputFinished = (code: string) => {
79
+ onChangeState('CODE_CHECKING');
80
+
81
+ onInputFinished(code);
82
+ };
83
+
84
+ const handleFatalErrorOkButtonClick = () => {
85
+ if (onFatalErrorOkButtonClick) {
86
+ onFatalErrorOkButtonClick();
87
+ }
88
+ };
89
+
90
+ // eslint-disable-next-line react/jsx-no-constructed-context-values
91
+ const contextValue: TConfirmationContext = {
92
+ hideCountdownSection,
93
+ alignContent,
94
+ texts: { ...defaultTexts, ...restProps.texts },
95
+ state,
96
+ screen,
97
+ requiredCharAmount,
98
+ countdownDuration,
99
+ timeLeft,
100
+ tempBlockDuration,
101
+ phone,
102
+ blockSmsRetry,
103
+ breakpoint,
104
+ onTempBlockFinished,
105
+ onChangeState,
106
+ onChangeScreen,
107
+ clearCodeOnError,
108
+ initialScreenHintSlot,
109
+ onInputFinished: handleInputFinished,
110
+ onSmsRetryClick: handleSmsRetry,
111
+ onFatalErrorOkButtonClick: handleFatalErrorOkButtonClick,
112
+ };
113
+
114
+ const screensMap = getScreensMap ? getScreensMap(confirmationScreens) : confirmationScreens;
115
+
116
+ const CurrentScreen = screensMap[screen];
117
+
118
+ return (
119
+ <ConfirmationContext.Provider value={contextValue}>
120
+ <div className={cn(styles.component, className)} data-test-id={dataTestId}>
121
+ {CurrentScreen && <CurrentScreen mobile={mobile} />}
122
+ </div>
123
+ </ConfirmationContext.Provider>
124
+ );
125
+ };
@@ -0,0 +1,8 @@
1
+ @import '@alfalab/core-components-themes/src/default.css';
2
+
3
+ .component {
4
+ width: 100%;
5
+ max-width: 388px;
6
+ margin: 0 auto;
7
+ color: var(--color-light-text-secondary);
8
+ }
@@ -0,0 +1 @@
1
+ export * from './component';
@@ -0,0 +1,37 @@
1
+ import React, { FC } from 'react';
2
+
3
+ import styles from './index.module.css';
4
+
5
+ type Props = {
6
+ progress: number; // 0-1
7
+ className?: string;
8
+ };
9
+
10
+ const SIZE = 16;
11
+ const STROKE_WIDTH = 2;
12
+
13
+ const CENTER = SIZE / 2;
14
+ const RADIUS = CENTER - STROKE_WIDTH / 2;
15
+ /** Длина окружности */
16
+ const CIRCUMFERENCE = Math.PI * RADIUS * 2;
17
+
18
+ export const CountdownLoader: FC<Props> = ({ progress, className }) => {
19
+ const value = Math.min(progress, 1);
20
+
21
+ const strokeDasharray = CIRCUMFERENCE.toFixed(2);
22
+ const strokeDashoffset = (value * CIRCUMFERENCE).toFixed(2);
23
+
24
+ return (
25
+ <svg width={SIZE} height={SIZE} viewBox={`0 0 ${SIZE} ${SIZE}`} className={className}>
26
+ <circle
27
+ cx={CENTER}
28
+ cy={CENTER}
29
+ r={RADIUS}
30
+ strokeDasharray={strokeDasharray}
31
+ strokeDashoffset={strokeDashoffset}
32
+ transform={`rotate(-90 ${CENTER} ${CENTER})`}
33
+ className={styles.circle}
34
+ />
35
+ </svg>
36
+ );
37
+ };
@@ -0,0 +1,7 @@
1
+ @import '@alfalab/core-components-themes/src/default.css';
2
+
3
+ .circle {
4
+ fill: transparent;
5
+ stroke: var(--color-light-graphic-tertiary);
6
+ stroke-width: 2px;
7
+ }
@@ -0,0 +1 @@
1
+ export * from './component';
@@ -0,0 +1,28 @@
1
+ import React, { FC, ReactNode } from 'react';
2
+ import cn from 'classnames';
3
+
4
+ import { Typography } from '@alfalab/core-components-typography';
5
+
6
+ import styles from './index.module.css';
7
+
8
+ export type HeaderProps = {
9
+ /**
10
+ * Контент шапки
11
+ */
12
+ children?: ReactNode;
13
+
14
+ /**
15
+ * Отображать в мобильной версии экран компонента
16
+ */
17
+ mobile?: boolean;
18
+ };
19
+
20
+ export const Header: FC<HeaderProps> = ({ mobile, children }) => (
21
+ <Typography.Title
22
+ className={cn(styles.header, styles.typography, { [styles.typographyMobile]: mobile })}
23
+ tag='h3'
24
+ color='primary'
25
+ >
26
+ {children}
27
+ </Typography.Title>
28
+ );
@@ -0,0 +1,21 @@
1
+ @import '../../vars.css';
2
+
3
+ .header.header {
4
+ margin-bottom: var(--gap-xl);
5
+ }
6
+
7
+ .typography.typography.typography {
8
+ font-feature-settings: var(--confirmation-header-font-feature-settings);
9
+ font-size: var(--confirmation-header-desktop-font-size);
10
+ line-height: var(--confirmation-header-desktop-line-height);
11
+ font-weight: var(--confirmation-header-desktop-font-weight);
12
+ font-family: var(--confirmation-header-desktop-font-family);
13
+ }
14
+
15
+ .typographyMobile.typographyMobile.typographyMobile {
16
+ font-feature-settings: var(--confirmation-header-font-feature-settings);
17
+ font-size: var(--confirmation-header-mobile-font-size);
18
+ line-height: var(--confirmation-header-mobile-line-height);
19
+ font-weight: var(--confirmation-header-mobile-font-weight);
20
+ font-family: var(--confirmation-header-mobile-font-family);
21
+ }
@@ -0,0 +1 @@
1
+ export * from './component';
@@ -0,0 +1,2 @@
1
+ export * from './screens';
2
+ export * from './countdown-loader';
@@ -0,0 +1,46 @@
1
+ import React, { FC, useContext } from 'react';
2
+ import cn from 'classnames';
3
+
4
+ import { Button } from '@alfalab/core-components-button';
5
+ import { Typography } from '@alfalab/core-components-typography';
6
+
7
+ import { ConfirmationContext } from '../../../context';
8
+ import { Header } from '../../header';
9
+
10
+ import styles from './index.module.css';
11
+
12
+ export type FatalErrorProps = {
13
+ /**
14
+ * Отображать в мобильной версии экран компонента
15
+ */
16
+ mobile?: boolean;
17
+ };
18
+
19
+ export const FatalError: FC<FatalErrorProps> = ({ mobile }) => {
20
+ const { alignContent, texts, onFatalErrorOkButtonClick, breakpoint } =
21
+ useContext(ConfirmationContext);
22
+
23
+ return (
24
+ <div className={cn(styles.component, styles[alignContent])}>
25
+ <Header mobile={mobile}>{texts.fatalErrorTitle}</Header>
26
+
27
+ <Typography.Text
28
+ view='primary-medium'
29
+ color='primary'
30
+ className={cn({ [styles.typographyTheme]: !mobile })}
31
+ >
32
+ {texts.fatalErrorDescription}
33
+ </Typography.Text>
34
+
35
+ <Button
36
+ size={mobile ? 'xs' : 's'}
37
+ view='secondary'
38
+ onClick={onFatalErrorOkButtonClick}
39
+ className={styles.button}
40
+ breakpoint={breakpoint}
41
+ >
42
+ {texts.fatalErrorButton}
43
+ </Button>
44
+ </div>
45
+ );
46
+ };
@@ -0,0 +1,23 @@
1
+ @import '../../../vars.css';
2
+
3
+ .component {
4
+ display: flex;
5
+ flex-direction: column;
6
+ }
7
+
8
+ .typographyTheme.typographyTheme {
9
+ @mixin _confirmation-text;
10
+ }
11
+
12
+ .left {
13
+ align-items: flex-start;
14
+ }
15
+
16
+ .center {
17
+ align-items: center;
18
+ text-align: center;
19
+ }
20
+
21
+ .button {
22
+ margin-top: var(--gap-2xl);
23
+ }
@@ -0,0 +1 @@
1
+ export * from './component';
@@ -0,0 +1,94 @@
1
+ import React, { FC, useContext } from 'react';
2
+ import cn from 'classnames';
3
+
4
+ import { Button } from '@alfalab/core-components-button';
5
+ import { Link } from '@alfalab/core-components-link';
6
+ import { Typography } from '@alfalab/core-components-typography';
7
+
8
+ import { ConfirmationContext } from '../../../context';
9
+ import { Header } from '../../header';
10
+
11
+ import styles from './index.module.css';
12
+
13
+ export type HintProps = {
14
+ /**
15
+ * Отображать в мобильной версии экран компонента
16
+ */
17
+ mobile?: boolean;
18
+ };
19
+
20
+ export const Hint: FC<HintProps> = ({ mobile }) => {
21
+ const { alignContent, texts, onChangeScreen, onChangeState, breakpoint } =
22
+ useContext(ConfirmationContext);
23
+
24
+ const handleReturnButtonClick = () => {
25
+ onChangeScreen('INITIAL');
26
+ onChangeState('INITIAL');
27
+ };
28
+
29
+ return (
30
+ <div className={cn(styles.component, styles[alignContent])}>
31
+ <Header mobile={mobile}>Не&nbsp;приходит сообщение?</Header>
32
+
33
+ <Typography.Text
34
+ view='primary-medium'
35
+ color='primary'
36
+ className={cn(styles.text, { [styles.typographyTheme]: !mobile })}
37
+ >
38
+ Если у&nbsp;вас изменился номер телефона, позвоните нам или обратитесь в&nbsp;любое
39
+ отделение банка.
40
+ </Typography.Text>
41
+
42
+ <div className={styles.phonesWrap}>
43
+ <div className={cn(styles.phoneWrap, { [styles.phoneContentMobile]: mobile })}>
44
+ <Link href='tel:+78002000000' underline={false} className={styles.phoneLink}>
45
+ 8 800 200-00-00
46
+ </Link>
47
+ <Typography.Text
48
+ view='primary-medium'
49
+ color='primary'
50
+ className={cn(styles.text, { [styles.typographyTheme]: !mobile })}
51
+ >
52
+ {mobile
53
+ ? 'Для\u00A0звонков по\u00A0России'
54
+ : ' \u2014\u00A0для звонков по\u00A0России'}
55
+ </Typography.Text>
56
+ </div>
57
+
58
+ <div className={cn(styles.phoneWrap, { [styles.phoneContentMobile]: mobile })}>
59
+ <Link href='tel:+74957888878' underline={false} className={styles.phoneLink}>
60
+ +7 495 78-888-78
61
+ </Link>
62
+ <Typography.Text
63
+ view='primary-medium'
64
+ color='primary'
65
+ className={cn(styles.text, { [styles.typographyTheme]: !mobile })}
66
+ >
67
+ {mobile
68
+ ? 'В\u00A0Москве и\u00A0за\u00A0границей'
69
+ : ' \u2014\u00A0в\u00A0Москве и\u00A0за\u00A0границей'}
70
+ </Typography.Text>
71
+ </div>
72
+ </div>
73
+
74
+ <Typography.Text
75
+ view='primary-medium'
76
+ color='primary'
77
+ className={cn(styles.text, { [styles.typographyTheme]: !mobile })}
78
+ >
79
+ Если номер не&nbsp;менялся, возможно, перегружен сервис отправки сообщений.
80
+ Попробуйте повторить действие через несколько минут.
81
+ </Typography.Text>
82
+
83
+ <Button
84
+ size={mobile ? 'xs' : 's'}
85
+ view='secondary'
86
+ onClick={handleReturnButtonClick}
87
+ className={styles.hintButton}
88
+ breakpoint={breakpoint}
89
+ >
90
+ {texts.hintButton}
91
+ </Button>
92
+ </div>
93
+ );
94
+ };
@@ -0,0 +1,51 @@
1
+ @import '../../../vars.css';
2
+
3
+ .component {
4
+ display: flex;
5
+ flex-direction: column;
6
+ align-items: flex-start;
7
+ & span:last-of-type {
8
+ margin-bottom: 0;
9
+ }
10
+ }
11
+
12
+ .center {
13
+ align-items: center;
14
+ text-align: center;
15
+ }
16
+
17
+ .text.text {
18
+ margin-bottom: var(--gap-xl);
19
+ }
20
+
21
+ .typographyTheme.typographyTheme {
22
+ @mixin _confirmation-text;
23
+ }
24
+
25
+ .phonesWrap {
26
+ display: flex;
27
+ flex-direction: column;
28
+ margin-bottom: var(--gap-xl);
29
+ }
30
+
31
+ .phoneWrap {
32
+ margin-bottom: var(--gap-xs);
33
+
34
+ &:last-child {
35
+ margin-bottom: 0;
36
+ }
37
+ }
38
+
39
+ .phoneLink.phoneLink {
40
+ @mixin action_primary_medium;
41
+ }
42
+
43
+ .hintButton {
44
+ margin-top: var(--gap-2xl);
45
+ }
46
+
47
+ .phoneContentMobile {
48
+ display: flex;
49
+ flex-direction: column;
50
+ align-items: center;
51
+ }
@@ -0,0 +1 @@
1
+ export * from './component';
@@ -0,0 +1,4 @@
1
+ export * from './initial';
2
+ export * from './hint';
3
+ export * from './fatal-error';
4
+ export * from './temp-block';
@@ -0,0 +1,203 @@
1
+ import React, {
2
+ FC,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import cn from 'classnames';
11
+
12
+ import { ButtonMobile } from '@alfalab/core-components-button/mobile';
13
+ import { CodeInputDesktop } from '@alfalab/core-components-code-input/desktop';
14
+ import { CodeInputMobile } from '@alfalab/core-components-code-input/mobile';
15
+ import { BaseCodeInputProps, CustomInputRef } from '@alfalab/core-components-code-input/shared';
16
+ import { Link } from '@alfalab/core-components-link';
17
+ import { Typography } from '@alfalab/core-components-typography';
18
+ import { usePrevious } from '@alfalab/hooks';
19
+
20
+ import { ConfirmationContext } from '../../../context';
21
+ import { Header } from '../../header';
22
+
23
+ import { CountdownSection } from './countdown-section';
24
+
25
+ import styles from './index.module.css';
26
+
27
+ export type InitialProps = {
28
+ /**
29
+ * Отображать в мобильной версии экран компонента
30
+ */
31
+ mobile?: boolean;
32
+ };
33
+
34
+ const CODE_SEND_HINT_VISIBLE_DURATION = 2000;
35
+
36
+ export const Initial: FC<InitialProps> = ({ mobile }) => {
37
+ const {
38
+ state,
39
+ alignContent,
40
+ texts,
41
+ requiredCharAmount,
42
+ timeLeft,
43
+ phone,
44
+ clearCodeOnError,
45
+ hideCountdownSection,
46
+ initialScreenHintSlot,
47
+ onChangeState,
48
+ onInputFinished,
49
+ onChangeScreen,
50
+ onSmsRetryClick,
51
+ } = useContext(ConfirmationContext);
52
+
53
+ const prevState = usePrevious(state);
54
+
55
+ const inputRef = useRef<CustomInputRef>(null);
56
+
57
+ const [codeSendHintVisible, setCodeSendHintVisible] = useState(false);
58
+
59
+ const timerId = useRef(0);
60
+
61
+ const handleInputComplete: BaseCodeInputProps['onComplete'] = (code) => {
62
+ onInputFinished(code);
63
+ };
64
+
65
+ const handleSmsHintLinkClick = () => {
66
+ onChangeScreen('HINT');
67
+ };
68
+
69
+ const handleInputChange = () => {
70
+ if (state === 'CODE_ERROR') {
71
+ onChangeState('INITIAL');
72
+ }
73
+ };
74
+
75
+ const handleSmsRetryClick = () => {
76
+ if (inputRef.current) {
77
+ inputRef.current.reset();
78
+ }
79
+
80
+ onSmsRetryClick();
81
+ };
82
+
83
+ const handleErrorAnimationEnd = () => {
84
+ if (clearCodeOnError && state !== 'INITIAL') {
85
+ onChangeState('INITIAL');
86
+ }
87
+ };
88
+
89
+ const clearTimer = useCallback(() => {
90
+ window.clearTimeout(timerId.current);
91
+ }, []);
92
+
93
+ useEffect(() => {
94
+ if (inputRef.current) {
95
+ inputRef.current.focus();
96
+ }
97
+
98
+ return () => {
99
+ clearTimer();
100
+ };
101
+ }, [clearTimer]);
102
+
103
+ useEffect(() => {
104
+ if (!inputRef.current) {
105
+ return;
106
+ }
107
+
108
+ if (state === 'CODE_ERROR' && prevState !== 'CODE_ERROR') {
109
+ inputRef.current.focus(requiredCharAmount - 1);
110
+ }
111
+
112
+ if (prevState === 'CODE_SENDING' && state !== 'CODE_SENDING') {
113
+ inputRef.current.focus();
114
+ }
115
+ }, [prevState, state, requiredCharAmount]);
116
+
117
+ useLayoutEffect(() => {
118
+ if (prevState === 'CODE_SENDING' && state !== 'CODE_SENDING') {
119
+ setCodeSendHintVisible(true);
120
+
121
+ clearTimer();
122
+
123
+ timerId.current = window.setTimeout(() => {
124
+ setCodeSendHintVisible(false);
125
+ }, CODE_SEND_HINT_VISIBLE_DURATION);
126
+ }
127
+ }, [prevState, state, clearTimer]);
128
+
129
+ const getCodeInputError = (): string | boolean => {
130
+ if (state === 'CODE_ERROR') {
131
+ return texts.codeError || true;
132
+ }
133
+
134
+ return false;
135
+ };
136
+
137
+ const renderHintButton = () => {
138
+ if (initialScreenHintSlot) {
139
+ return initialScreenHintSlot;
140
+ }
141
+
142
+ if (mobile) {
143
+ return (
144
+ <ButtonMobile onClick={handleSmsHintLinkClick} view='link' size='xs'>
145
+ {texts.linkToHint}
146
+ </ButtonMobile>
147
+ );
148
+ }
149
+
150
+ return (
151
+ <Link
152
+ onClick={handleSmsHintLinkClick}
153
+ className={styles.smsComeLink}
154
+ view={mobile ? 'primary' : 'secondary'}
155
+ pseudo={true}
156
+ >
157
+ {texts.linkToHint}
158
+ </Link>
159
+ );
160
+ };
161
+
162
+ const processing = ['CODE_CHECKING', 'CODE_SENDING'].includes(state);
163
+
164
+ const timePassed = timeLeft === 0;
165
+
166
+ const ComponentCodeInput = mobile ? CodeInputMobile : CodeInputDesktop;
167
+
168
+ return (
169
+ <div className={cn(styles.component, styles[alignContent])}>
170
+ <Header mobile={mobile}>{texts.title}</Header>
171
+
172
+ <Typography.Text
173
+ view='primary-medium'
174
+ color='primary'
175
+ className={cn(styles.phone, { [styles.typographyTheme]: !mobile })}
176
+ >
177
+ Код отправлен на {phone}
178
+ </Typography.Text>
179
+ <ComponentCodeInput
180
+ disabled={processing}
181
+ error={getCodeInputError()}
182
+ ref={inputRef}
183
+ fields={requiredCharAmount}
184
+ className={cn(styles.containerInput, styles.codeInput)}
185
+ onComplete={handleInputComplete}
186
+ onChange={handleInputChange}
187
+ clearCodeOnError={clearCodeOnError}
188
+ onErrorAnimationEnd={handleErrorAnimationEnd}
189
+ />
190
+ {!hideCountdownSection && (
191
+ <CountdownSection
192
+ processing={processing}
193
+ timePassed={timePassed}
194
+ codeSendHintVisible={codeSendHintVisible}
195
+ handleSmsRetryClick={handleSmsRetryClick}
196
+ mobile={mobile}
197
+ />
198
+ )}
199
+
200
+ {renderHintButton()}
201
+ </div>
202
+ );
203
+ };