@incodetech/web 2.0.0-alpha.1 → 2.0.0-alpha.11

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 (209) hide show
  1. package/dist/base.css +1071 -0
  2. package/dist/browser-ponyfill-B6W6hHVY.js +344 -0
  3. package/dist/email/email.es.js +145 -0
  4. package/dist/email/styles.css +123 -0
  5. package/dist/flow/flow.es.js +555 -0
  6. package/dist/flow/styles.css +1119 -0
  7. package/dist/incodeModule-Dv8Qllrv.js +254 -0
  8. package/dist/index.es.js +6 -0
  9. package/dist/instance-B-q0ZREN.js +2140 -0
  10. package/dist/otpInput-BtoZe0Wz.js +151 -0
  11. package/dist/page-Dh_Zw2ik.js +234 -0
  12. package/dist/phone/phone.es.js +3441 -0
  13. package/dist/phone/styles.css +305 -0
  14. package/dist/selfie/selfie.es.js +893 -0
  15. package/dist/selfie/styles.css +590 -0
  16. package/dist/selfieTutorial-C-u5GufD.js +29 -0
  17. package/dist/setup-wNL83jmW.js +20 -0
  18. package/dist/themes/dark.css +652 -0
  19. package/dist/themes/light.css +543 -0
  20. package/dist/title-BfO5Dlzk.js +25 -0
  21. package/dist/types/base.d.ts +1 -0
  22. package/dist/types/dark.d.ts +1 -0
  23. package/dist/types/email.d.ts +57 -0
  24. package/dist/types/flow.d.ts +69 -0
  25. package/dist/types/index.d.ts +38 -0
  26. package/dist/types/light.d.ts +1 -0
  27. package/dist/types/phone.d.ts +58 -0
  28. package/dist/types/selfie.d.ts +31 -0
  29. package/dist/types/styles.d.ts +1 -0
  30. package/dist/types/themes/dark.d.ts +1 -0
  31. package/dist/types/themes/light.d.ts +1 -0
  32. package/dist/uiConfig-CQ1W9cUD.js +23 -0
  33. package/dist/vendor-preact-CK0WeTOR.js +584 -0
  34. package/package.json +32 -26
  35. package/dev/README.md +0 -163
  36. package/dev/getToken.ts +0 -36
  37. package/dev/headless.html +0 -875
  38. package/dev/index.html +0 -366
  39. package/dev/main-headless.tsx +0 -1332
  40. package/dev/main-orchestrated-flow.tsx +0 -1158
  41. package/dev/main-preact.tsx +0 -323
  42. package/dev/main-simplified.tsx +0 -123
  43. package/dev/main-web-component.tsx +0 -256
  44. package/dev/main.tsx +0 -332
  45. package/dev/manual.html +0 -27
  46. package/dev/orchestrated-flow.html +0 -64
  47. package/dev/simplified.html +0 -64
  48. package/dev/tiktok-logo.svg +0 -7
  49. package/src/defineCustomElement.tsx +0 -30
  50. package/src/email/email.test.tsx +0 -368
  51. package/src/email/email.tsx +0 -255
  52. package/src/email/emailInput.test.tsx +0 -264
  53. package/src/email/emailInput.tsx +0 -85
  54. package/src/email/styles.css +0 -59
  55. package/src/flow/flow.test.tsx +0 -796
  56. package/src/flow/flow.tsx +0 -292
  57. package/src/flow/flowCompleted.css +0 -30
  58. package/src/flow/flowCompleted.test.tsx +0 -331
  59. package/src/flow/flowCompleted.tsx +0 -121
  60. package/src/flow/flowInit.test.ts +0 -264
  61. package/src/flow/flowInit.ts +0 -94
  62. package/src/flow/flowStart.css +0 -58
  63. package/src/flow/flowStart.test.tsx +0 -49
  64. package/src/flow/flowStart.tsx +0 -41
  65. package/src/flow/incode-logo.svg +0 -8
  66. package/src/flow/index.ts +0 -7
  67. package/src/flow/preloadFlow.test.ts +0 -421
  68. package/src/flow/preloadFlow.ts +0 -171
  69. package/src/flow/styles.css +0 -9
  70. package/src/flow/unsupportedModule.css +0 -21
  71. package/src/flow/unsupportedModule.tsx +0 -39
  72. package/src/flow/useFlowInitialization.test.tsx +0 -292
  73. package/src/flow/useFlowInitialization.ts +0 -128
  74. package/src/flow/useModuleLoader.test.tsx +0 -212
  75. package/src/flow/useModuleLoader.ts +0 -92
  76. package/src/hooks/index.ts +0 -1
  77. package/src/hooks/useManager.test.ts +0 -91
  78. package/src/hooks/useManager.ts +0 -40
  79. package/src/i18n/index.ts +0 -3
  80. package/src/i18n/instance.ts +0 -16
  81. package/src/i18n/setup.ts +0 -184
  82. package/src/i18n/useTranslation.ts +0 -42
  83. package/src/index.ts +0 -27
  84. package/src/permissions/assets/android-dots-icon.svg +0 -7
  85. package/src/permissions/assets/android-settings-icon.svg +0 -16
  86. package/src/permissions/assets/android-toggle-icon.svg +0 -20
  87. package/src/permissions/assets/bank-card-icon.svg +0 -14
  88. package/src/permissions/assets/camera-icon.svg +0 -12
  89. package/src/permissions/assets/camera-ios.svg +0 -53
  90. package/src/permissions/assets/check-icon.svg +0 -8
  91. package/src/permissions/assets/chrome-icon.svg +0 -43
  92. package/src/permissions/assets/password-icon.svg +0 -11
  93. package/src/permissions/assets/permissions-img.svg +0 -51
  94. package/src/permissions/assets/safari-icon.svg +0 -37
  95. package/src/permissions/assets/settings-icon.svg +0 -33
  96. package/src/permissions/assets/toggle-icon.svg +0 -19
  97. package/src/permissions/assets/warning-icon.svg +0 -6
  98. package/src/permissions/boldWithArrow.css +0 -9
  99. package/src/permissions/boldWithArrow.tsx +0 -41
  100. package/src/permissions/denied.css +0 -37
  101. package/src/permissions/denied.tsx +0 -29
  102. package/src/permissions/deniedAndroid.tsx +0 -56
  103. package/src/permissions/deniedDesktop.css +0 -9
  104. package/src/permissions/deniedDesktop.tsx +0 -64
  105. package/src/permissions/deniedIOS.tsx +0 -73
  106. package/src/permissions/deniedInstructions.tsx +0 -19
  107. package/src/permissions/iconWrapper.css +0 -9
  108. package/src/permissions/iconWrapper.tsx +0 -15
  109. package/src/permissions/learnMore.css +0 -37
  110. package/src/permissions/learnMore.tsx +0 -85
  111. package/src/permissions/numberedStep.css +0 -13
  112. package/src/permissions/numberedStep.tsx +0 -14
  113. package/src/permissions/permissions.css +0 -13
  114. package/src/permissions/permissions.tsx +0 -68
  115. package/src/phone/phone.tsx +0 -246
  116. package/src/phone/phoneInput.test.tsx +0 -275
  117. package/src/phone/phoneInput.tsx +0 -249
  118. package/src/phone/styles.css +0 -158
  119. package/src/selfie/cameraButton.css +0 -13
  120. package/src/selfie/cameraButton.tsx +0 -35
  121. package/src/selfie/capture.css +0 -57
  122. package/src/selfie/capture.tsx +0 -232
  123. package/src/selfie/errorModal.tsx +0 -218
  124. package/src/selfie/errorModalContent.css +0 -33
  125. package/src/selfie/errorModalContent.tsx +0 -44
  126. package/src/selfie/faceOutline.css +0 -5
  127. package/src/selfie/faceOutline.tsx +0 -22
  128. package/src/selfie/loadingBorder.css +0 -12
  129. package/src/selfie/loadingBorder.tsx +0 -77
  130. package/src/selfie/manualCaptureButton.css +0 -13
  131. package/src/selfie/manualCaptureButton.tsx +0 -35
  132. package/src/selfie/noMoreAttemptsModal.tsx +0 -44
  133. package/src/selfie/notification.css +0 -9
  134. package/src/selfie/notification.tsx +0 -36
  135. package/src/selfie/retryErrorModal.tsx +0 -56
  136. package/src/selfie/selfie.test.tsx +0 -458
  137. package/src/selfie/selfie.tsx +0 -83
  138. package/src/selfie/selfieTutorial.json +0 -2626
  139. package/src/selfie/styles.css +0 -1
  140. package/src/selfie/tutorial.test.tsx +0 -200
  141. package/src/selfie/tutorial.tsx +0 -43
  142. package/src/setup.ts +0 -33
  143. package/src/shared/baseTutorial/baseTutorial.css +0 -21
  144. package/src/shared/baseTutorial/baseTutorial.test.tsx +0 -184
  145. package/src/shared/baseTutorial/baseTutorial.tsx +0 -55
  146. package/src/shared/baseTutorial/replaceBaseTutorial.test.ts +0 -267
  147. package/src/shared/baseTutorial/replaceBaseTutorial.ts +0 -68
  148. package/src/shared/button/button.css +0 -55
  149. package/src/shared/button/button.test.tsx +0 -101
  150. package/src/shared/button/button.tsx +0 -47
  151. package/src/shared/componentRoot/incodeComponent.tsx +0 -12
  152. package/src/shared/countries/countries.test.ts +0 -75
  153. package/src/shared/countries/countries.ts +0 -139
  154. package/src/shared/countries/index.ts +0 -6
  155. package/src/shared/icons/chevronDown.tsx +0 -22
  156. package/src/shared/icons/index.ts +0 -2
  157. package/src/shared/icons/successIcon.css +0 -5
  158. package/src/shared/icons/successIcon.test.tsx +0 -40
  159. package/src/shared/icons/successIcon.tsx +0 -26
  160. package/src/shared/loader/loadingIcon.css +0 -28
  161. package/src/shared/loader/loadingIcon.tsx +0 -67
  162. package/src/shared/lottie/lottie.tsx +0 -108
  163. package/src/shared/otpInput/otpInput.css +0 -85
  164. package/src/shared/otpInput/otpInput.test.tsx +0 -356
  165. package/src/shared/otpInput/otpInput.tsx +0 -241
  166. package/src/shared/page/incode-logo.svg +0 -3
  167. package/src/shared/page/page.css +0 -47
  168. package/src/shared/page/page.test.tsx +0 -97
  169. package/src/shared/page/page.tsx +0 -91
  170. package/src/shared/page/pageUiConfig.test.ts +0 -112
  171. package/src/shared/page/pageUiConfig.ts +0 -64
  172. package/src/shared/page/verifiedByIncode.css +0 -5
  173. package/src/shared/page/verifiedByIncode.tsx +0 -75
  174. package/src/shared/spacer/spacer.css +0 -149
  175. package/src/shared/spacer/spacer.test.tsx +0 -143
  176. package/src/shared/spacer/spacer.tsx +0 -88
  177. package/src/shared/spinner/index.ts +0 -2
  178. package/src/shared/spinner/spinner.css +0 -28
  179. package/src/shared/spinner/spinner.test.tsx +0 -82
  180. package/src/shared/spinner/spinner.tsx +0 -65
  181. package/src/shared/title/title.css +0 -7
  182. package/src/shared/title/title.tsx +0 -12
  183. package/src/shared/uiConfig/uiConfig.ts +0 -36
  184. package/src/shared/webComponent/incodeModule.ts +0 -29
  185. package/src/shared/webComponent/registerIncodeElement.ts +0 -15
  186. package/src/styles/__mocks__/fetchTheme.ts +0 -19
  187. package/src/styles/applyTheme.ts +0 -37
  188. package/src/styles/cn.test.tsx +0 -57
  189. package/src/styles/cn.tsx +0 -21
  190. package/src/styles/core.css +0 -12
  191. package/src/styles/fetchTheme.test.ts +0 -390
  192. package/src/styles/fetchTheme.ts +0 -88
  193. package/src/styles/generatePalette.ts +0 -111
  194. package/src/styles/reset.css +0 -65
  195. package/src/styles/resolveCssVariableToHex.ts +0 -28
  196. package/src/styles/tailwind.css +0 -291
  197. package/src/styles/themeTypes.ts +0 -18
  198. package/src/styles/tokens/colors.css +0 -190
  199. package/src/styles/tokens/components.css +0 -174
  200. package/src/styles/tokens/index.css +0 -4
  201. package/src/styles/tokens/primitives.css +0 -129
  202. package/src/styles/tokens/semantic.css +0 -51
  203. package/src/svg.d.ts +0 -4
  204. package/src/types/assets.d.ts +0 -1
  205. package/src/types/custom-elements.d.ts +0 -104
  206. package/tsconfig.json +0 -22
  207. package/vite.config.ts +0 -260
  208. package/vitest.config.ts +0 -40
  209. package/vitest.setup.ts +0 -16
@@ -1,275 +0,0 @@
1
- import { fireEvent, render, screen } from '@testing-library/preact';
2
- import { beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { PhoneInput } from './phoneInput';
4
-
5
- describe('PhoneInput', () => {
6
- const defaultProps = {
7
- countryCode: 'US',
8
- phonePrefix: '+1',
9
- onPhoneChange: vi.fn(),
10
- };
11
-
12
- beforeEach(() => {
13
- vi.clearAllMocks();
14
- });
15
-
16
- describe('Rendering', () => {
17
- it('renders phone input field', () => {
18
- render(<PhoneInput {...defaultProps} />);
19
-
20
- const input = screen.getByTestId('phone-input');
21
- expect(input).toBeTruthy();
22
- });
23
-
24
- it('renders country selector button', () => {
25
- render(<PhoneInput {...defaultProps} />);
26
-
27
- const selector = screen.getByTestId('phone-country-selector');
28
- expect(selector).toBeTruthy();
29
- });
30
-
31
- it('renders with initial country code', () => {
32
- render(<PhoneInput {...defaultProps} countryCode="MX" />);
33
-
34
- const selector = screen.getByTestId('phone-country-selector');
35
- expect(selector.textContent).toContain('+52');
36
- });
37
-
38
- it('renders US flag emoji for US country', () => {
39
- render(<PhoneInput {...defaultProps} countryCode="US" />);
40
-
41
- const selector = screen.getByTestId('phone-country-selector');
42
- expect(selector.textContent).toContain('🇺🇸');
43
- });
44
- });
45
-
46
- describe('Phone input', () => {
47
- it('calls onPhoneChange when input changes', () => {
48
- const onPhoneChange = vi.fn();
49
- render(<PhoneInput {...defaultProps} onPhoneChange={onPhoneChange} />);
50
-
51
- const input = screen.getByTestId('phone-input');
52
- fireEvent.input(input, { target: { value: '4155551234' } });
53
-
54
- expect(onPhoneChange).toHaveBeenCalled();
55
- });
56
-
57
- it('formats phone number as user types', () => {
58
- render(<PhoneInput {...defaultProps} />);
59
-
60
- const input = screen.getByTestId('phone-input') as HTMLInputElement;
61
- fireEvent.input(input, { target: { value: '4155551234' } });
62
-
63
- // Should be formatted with AsYouType
64
- expect(input.value).toMatch(/[\d\s()-]+/);
65
- });
66
-
67
- it('validates phone number and reports validity', () => {
68
- const onPhoneChange = vi.fn();
69
- render(<PhoneInput {...defaultProps} onPhoneChange={onPhoneChange} />);
70
-
71
- const input = screen.getByTestId('phone-input');
72
-
73
- // Invalid phone (too short)
74
- fireEvent.input(input, { target: { value: '123' } });
75
- expect(onPhoneChange).toHaveBeenLastCalledWith(expect.any(String), false);
76
-
77
- // Valid US phone
78
- fireEvent.input(input, { target: { value: '4155551234' } });
79
- expect(onPhoneChange).toHaveBeenLastCalledWith(expect.any(String), true);
80
- });
81
- });
82
-
83
- describe('Country selector', () => {
84
- it('opens dropdown on click', () => {
85
- render(<PhoneInput {...defaultProps} />);
86
-
87
- const selector = screen.getByTestId('phone-country-selector');
88
- fireEvent.click(selector);
89
-
90
- // Dropdown should be visible
91
- const dropdown = document.querySelector('.IncodePhoneCountryDropdown');
92
- expect(dropdown).toBeTruthy();
93
- });
94
-
95
- it('closes dropdown when clicking outside', () => {
96
- render(<PhoneInput {...defaultProps} />);
97
-
98
- const selector = screen.getByTestId('phone-country-selector');
99
- fireEvent.click(selector);
100
-
101
- // Click outside
102
- fireEvent.mouseDown(document.body);
103
-
104
- const dropdown = document.querySelector('.IncodePhoneCountryDropdown');
105
- expect(dropdown).toBeFalsy();
106
- });
107
-
108
- it('shows list of countries in dropdown', () => {
109
- render(<PhoneInput {...defaultProps} />);
110
-
111
- const selector = screen.getByTestId('phone-country-selector');
112
- fireEvent.click(selector);
113
-
114
- const options = document.querySelectorAll('.IncodePhoneCountryOption');
115
- expect(options.length).toBeGreaterThan(0);
116
- });
117
-
118
- it('changes country when option is selected', () => {
119
- const onPhoneChange = vi.fn();
120
- render(<PhoneInput {...defaultProps} onPhoneChange={onPhoneChange} />);
121
-
122
- const selector = screen.getByTestId('phone-country-selector');
123
- fireEvent.click(selector);
124
-
125
- // Find and click Mexico option
126
- const options = document.querySelectorAll('.IncodePhoneCountryOption');
127
- const mxOption = Array.from(options).find((opt) =>
128
- opt.textContent?.includes('Mexico'),
129
- );
130
-
131
- if (mxOption) {
132
- fireEvent.click(mxOption);
133
- }
134
-
135
- // Dropdown should close and country should change
136
- expect(selector.textContent).toContain('+52');
137
- });
138
-
139
- it('does not open dropdown when disabled', () => {
140
- render(<PhoneInput {...defaultProps} disabled />);
141
-
142
- const selector = screen.getByTestId('phone-country-selector');
143
- fireEvent.click(selector);
144
-
145
- const dropdown = document.querySelector('.IncodePhoneCountryDropdown');
146
- expect(dropdown).toBeFalsy();
147
- });
148
- });
149
-
150
- describe('Prefilled phone', () => {
151
- it('populates input with prefilled phone', () => {
152
- render(<PhoneInput {...defaultProps} prefilledPhone="+14155551234" />);
153
-
154
- const input = screen.getByTestId('phone-input') as HTMLInputElement;
155
- expect(input.value).toBeTruthy();
156
- });
157
-
158
- it('calls onPhoneChange with prefilled phone', () => {
159
- const onPhoneChange = vi.fn();
160
- render(
161
- <PhoneInput
162
- {...defaultProps}
163
- prefilledPhone="+14155551234"
164
- onPhoneChange={onPhoneChange}
165
- />,
166
- );
167
-
168
- // Should have called onPhoneChange with the prefilled value
169
- expect(onPhoneChange).toHaveBeenCalledWith('+14155551234', true);
170
- });
171
- });
172
-
173
- describe('Error state', () => {
174
- it('displays error message when phoneError is set', () => {
175
- render(
176
- <PhoneInput {...defaultProps} phoneError="Invalid phone number" />,
177
- );
178
-
179
- expect(screen.getByTestId('phone-error')).toBeTruthy();
180
- expect(screen.getByText('Invalid phone number')).toBeTruthy();
181
- });
182
-
183
- it('shows error styling on input wrapper', () => {
184
- const { container } = render(
185
- <PhoneInput {...defaultProps} phoneError="Error" />,
186
- );
187
-
188
- const wrapper = container.querySelector('.IncodePhoneInputWrapperError');
189
- expect(wrapper).toBeTruthy();
190
- });
191
- });
192
-
193
- describe('Opt-in checkbox', () => {
194
- it('does not show opt-in when optinEnabled is false', () => {
195
- render(<PhoneInput {...defaultProps} optinEnabled={false} />);
196
-
197
- expect(screen.queryByTestId('phone-optin')).toBeNull();
198
- });
199
-
200
- it('shows opt-in checkbox when optinEnabled is true', () => {
201
- render(<PhoneInput {...defaultProps} optinEnabled />);
202
-
203
- expect(screen.getByTestId('phone-optin')).toBeTruthy();
204
- });
205
-
206
- it('calls onOptInChange when checkbox is toggled', () => {
207
- const onOptInChange = vi.fn();
208
- render(
209
- <PhoneInput
210
- {...defaultProps}
211
- optinEnabled
212
- onOptInChange={onOptInChange}
213
- />,
214
- );
215
-
216
- const checkbox = screen.getByTestId('phone-optin');
217
- fireEvent.change(checkbox, { target: { checked: true } });
218
-
219
- expect(onOptInChange).toHaveBeenCalledWith(true);
220
- });
221
- });
222
-
223
- describe('Disabled state', () => {
224
- it('disables input when disabled prop is true', () => {
225
- render(<PhoneInput {...defaultProps} disabled />);
226
-
227
- const input = screen.getByTestId('phone-input') as HTMLInputElement;
228
- expect(input.disabled).toBe(true);
229
- });
230
-
231
- it('disables country selector when disabled', () => {
232
- render(<PhoneInput {...defaultProps} disabled />);
233
-
234
- const button = screen.getByTestId('phone-country-selector');
235
- expect(button.hasAttribute('disabled')).toBe(true);
236
- });
237
-
238
- it('shows disabled styling', () => {
239
- const { container } = render(<PhoneInput {...defaultProps} disabled />);
240
-
241
- const wrapper = container.querySelector(
242
- '.IncodePhoneInputWrapperDisabled',
243
- );
244
- expect(wrapper).toBeTruthy();
245
- });
246
- });
247
-
248
- describe('Accessibility', () => {
249
- it('country selector button is accessible', () => {
250
- render(<PhoneInput {...defaultProps} />);
251
-
252
- // Button should be accessible - it contains flag and country code
253
- const button = screen.getByTestId('phone-country-selector');
254
- expect(button.tagName.toLowerCase()).toBe('button');
255
- expect(button.textContent).toContain('+1'); // Country code is visible
256
- });
257
-
258
- it('has aria-expanded attribute on country selector', () => {
259
- render(<PhoneInput {...defaultProps} />);
260
-
261
- const selector = screen.getByTestId('phone-country-selector');
262
- expect(selector.getAttribute('aria-expanded')).toBe('false');
263
-
264
- fireEvent.click(selector);
265
- expect(selector.getAttribute('aria-expanded')).toBe('true');
266
- });
267
-
268
- it('has aria-haspopup attribute on country selector', () => {
269
- render(<PhoneInput {...defaultProps} />);
270
-
271
- const selector = screen.getByTestId('phone-country-selector');
272
- expect(selector.getAttribute('aria-haspopup')).toBe('listbox');
273
- });
274
- });
275
- });
@@ -1,249 +0,0 @@
1
- import {
2
- AsYouType,
3
- type CountryCode,
4
- parsePhoneNumberFromString,
5
- } from 'libphonenumber-js';
6
- import type { FC } from 'preact/compat';
7
- import { useEffect, useRef, useState } from 'preact/hooks';
8
- import {
9
- type CountryData,
10
- countries,
11
- getCountryByCode,
12
- } from '../shared/countries';
13
- import { ChevronDown } from '../shared/icons';
14
- import { Spacer } from '../shared/spacer/spacer';
15
- import './styles.css';
16
-
17
- export type PhoneInputProps = {
18
- countryCode: string;
19
- phonePrefix: string;
20
- prefilledPhone?: string;
21
- phoneError?: string;
22
- optinEnabled?: boolean;
23
- disabled?: boolean;
24
- onPhoneChange: (phone: string, isValid: boolean) => void;
25
- onOptInChange?: (granted: boolean) => void;
26
- };
27
-
28
- export const PhoneInput: FC<PhoneInputProps> = ({
29
- countryCode,
30
- prefilledPhone,
31
- phoneError,
32
- optinEnabled = false,
33
- disabled = false,
34
- onPhoneChange,
35
- onOptInChange,
36
- }) => {
37
- const [selectedCountry, setSelectedCountry] = useState<CountryData | null>(
38
- null,
39
- );
40
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
41
- const [phone, setPhone] = useState('');
42
- const [optInGranted, setOptInGranted] = useState(false);
43
- const dropdownRef = useRef<HTMLDivElement>(null);
44
-
45
- // Initialize selected country from countryCode prop
46
- useEffect(() => {
47
- const country = getCountryByCode(countryCode);
48
- if (country) {
49
- setSelectedCountry(country);
50
- } else {
51
- // Default to US if not found
52
- setSelectedCountry(
53
- countries.find((c) => c.code === 'US') || countries[0],
54
- );
55
- }
56
- }, [countryCode]);
57
-
58
- // Handle prefilled phone
59
- useEffect(() => {
60
- if (prefilledPhone && selectedCountry) {
61
- // Parse the prefilled phone to extract national number
62
- const phoneToFormat = prefilledPhone.startsWith('+')
63
- ? prefilledPhone
64
- : `+${prefilledPhone}`;
65
- const parsed = parsePhoneNumberFromString(phoneToFormat);
66
-
67
- if (parsed?.isValid()) {
68
- // Update country if different
69
- if (parsed.country && parsed.country !== selectedCountry.code) {
70
- const newCountry = getCountryByCode(parsed.country);
71
- if (newCountry) {
72
- setSelectedCountry(newCountry);
73
- }
74
- }
75
- // Format and set the national number
76
- const formatter = new AsYouType(parsed.country);
77
- const formatted = formatter.input(parsed.nationalNumber);
78
- setPhone(formatted);
79
- onPhoneChange(phoneToFormat, true);
80
- }
81
- }
82
- }, [prefilledPhone, selectedCountry]);
83
-
84
- // Close dropdown when clicking outside
85
- useEffect(() => {
86
- const handleClickOutside = (e: MouseEvent) => {
87
- if (
88
- dropdownRef.current &&
89
- !dropdownRef.current.contains(e.target as Node)
90
- ) {
91
- setIsDropdownOpen(false);
92
- }
93
- };
94
-
95
- if (isDropdownOpen) {
96
- document.addEventListener('mousedown', handleClickOutside);
97
- }
98
- return () => document.removeEventListener('mousedown', handleClickOutside);
99
- }, [isDropdownOpen]);
100
-
101
- const validateAndNotify = (nationalNumber: string, country: CountryData) => {
102
- const fullNumber = `${country.callingCode}${nationalNumber.replace(/\D/g, '')}`;
103
- try {
104
- const parsed = parsePhoneNumberFromString(
105
- fullNumber,
106
- country.code as CountryCode,
107
- );
108
- const valid = parsed?.isValid() ?? false;
109
- onPhoneChange(fullNumber, valid);
110
- } catch {
111
- onPhoneChange(fullNumber, false);
112
- }
113
- };
114
-
115
- const handlePhoneChange = (e: Event) => {
116
- const target = e.target as HTMLInputElement;
117
- const rawValue = target.value;
118
-
119
- if (selectedCountry) {
120
- // Format as user types
121
- const formatter = new AsYouType(selectedCountry.code);
122
- const formatted = formatter.input(rawValue);
123
- setPhone(formatted);
124
- validateAndNotify(rawValue, selectedCountry);
125
- } else {
126
- setPhone(rawValue);
127
- }
128
- };
129
-
130
- const handleCountrySelect = (country: CountryData) => {
131
- setSelectedCountry(country);
132
- setIsDropdownOpen(false);
133
-
134
- // Revalidate with new country
135
- if (phone) {
136
- validateAndNotify(phone, country);
137
- }
138
- };
139
-
140
- const handleOptInChange = (e: Event) => {
141
- const target = e.target as HTMLInputElement;
142
- const granted = target.checked;
143
- setOptInGranted(granted);
144
- onOptInChange?.(granted);
145
- };
146
-
147
- const toggleDropdown = () => {
148
- if (!disabled) {
149
- setIsDropdownOpen(!isDropdownOpen);
150
- }
151
- };
152
-
153
- return (
154
- <div class="IncodePhoneInput">
155
- <div class="IncodePhoneInputContainer">
156
- <div class="IncodePhoneInputRow">
157
- {/* Country Selector */}
158
- <div
159
- class={`IncodePhoneCountrySelector ${isDropdownOpen ? 'IncodePhoneCountrySelectorOpen' : ''} ${disabled ? 'IncodePhoneCountrySelectorDisabled' : ''}`}
160
- ref={dropdownRef}
161
- >
162
- <button
163
- type="button"
164
- class="IncodePhoneCountryButton"
165
- onClick={toggleDropdown}
166
- disabled={disabled}
167
- aria-expanded={isDropdownOpen}
168
- aria-haspopup="listbox"
169
- data-testid="phone-country-selector"
170
- >
171
- <span class="IncodePhoneCountryFlag" aria-hidden="true">
172
- {selectedCountry?.emoji}
173
- </span>
174
- <span class="IncodePhoneCountryCode">
175
- {selectedCountry?.callingCode}
176
- </span>
177
- <ChevronDown
178
- class={`IncodePhoneCountryArrow ${isDropdownOpen ? 'IncodePhoneCountryArrowUp' : ''}`}
179
- />
180
- </button>
181
-
182
- {/* Country Dropdown */}
183
- {isDropdownOpen && (
184
- <div class="IncodePhoneCountryDropdown">
185
- {countries.map((country) => (
186
- <button
187
- key={country.code}
188
- type="button"
189
- class={`IncodePhoneCountryOption ${selectedCountry?.code === country.code ? 'IncodePhoneCountryOptionSelected' : ''}`}
190
- onClick={() => handleCountrySelect(country)}
191
- >
192
- <span class="IncodePhoneCountryFlag" aria-hidden="true">
193
- {country.emoji}
194
- </span>
195
- <span class="IncodePhoneCountryOptionCode">
196
- {country.callingCode}
197
- </span>
198
- <span class="IncodePhoneCountryOptionName">
199
- {country.name}
200
- </span>
201
- </button>
202
- ))}
203
- </div>
204
- )}
205
- </div>
206
-
207
- {/* Phone Input */}
208
- <div
209
- class={`IncodePhoneInputWrapper ${phoneError ? 'IncodePhoneInputWrapperError' : ''} ${disabled ? 'IncodePhoneInputWrapperDisabled' : ''}`}
210
- >
211
- <input
212
- type="tel"
213
- value={phone}
214
- onInput={handlePhoneChange}
215
- placeholder="(123) 456-7890"
216
- disabled={disabled}
217
- class="IncodePhoneInputField"
218
- data-testid="phone-input"
219
- />
220
- </div>
221
- </div>
222
-
223
- {phoneError && (
224
- <>
225
- <Spacer size={8} />
226
- <p class="IncodePhoneInputError" data-testid="phone-error">
227
- {phoneError}
228
- </p>
229
- </>
230
- )}
231
-
232
- {optinEnabled && (
233
- <>
234
- <Spacer size={16} />
235
- <label class="IncodePhoneInputOptIn">
236
- <input
237
- type="checkbox"
238
- checked={optInGranted}
239
- onChange={handleOptInChange}
240
- data-testid="phone-optin"
241
- />
242
- <span>I agree to receive SMS notifications</span>
243
- </label>
244
- </>
245
- )}
246
- </div>
247
- </div>
248
- );
249
- };
@@ -1,158 +0,0 @@
1
- @reference "../styles/tailwind.css";
2
-
3
- /* Phone Page Layout */
4
- .IncodePhonePage .IncodePageContent {
5
- @apply pt-32 pb-16;
6
- }
7
-
8
- .IncodePhoneInput {
9
- @apply flex flex-col items-center w-full;
10
- }
11
-
12
- .IncodePhoneInputContainer {
13
- @apply flex flex-col items-center w-full;
14
- }
15
-
16
- /* Row container for country selector and input */
17
- .IncodePhoneInputRow {
18
- @apply relative flex items-stretch gap-8 w-full max-w-[340px];
19
- }
20
-
21
- /* Phone Input Wrapper (separate box) */
22
- .IncodePhoneInputWrapper {
23
- @apply flex-1 flex items-center border border-input-border-default rounded-small bg-input-surface-default transition-colors duration-200;
24
- }
25
-
26
- .IncodePhoneInputWrapper:focus-within {
27
- @apply border-input-border-active;
28
- }
29
-
30
- .IncodePhoneInputWrapper.IncodePhoneInputWrapperError {
31
- @apply border-input-border-negative;
32
- }
33
-
34
- .IncodePhoneInputWrapper.IncodePhoneInputWrapperDisabled {
35
- @apply border-input-border-disabled bg-input-surface-disabled;
36
- }
37
-
38
- /* Country Selector (separate box) */
39
- .IncodePhoneCountrySelector {
40
- @apply static shrink-0 w-[124px];
41
- @apply border border-input-border-default rounded-small bg-input-surface-default;
42
- @apply transition-colors duration-200;
43
- }
44
-
45
- .IncodePhoneCountrySelector.IncodePhoneCountrySelectorOpen {
46
- @apply border-input-border-active;
47
- }
48
-
49
- .IncodePhoneCountrySelector.IncodePhoneCountrySelectorDisabled {
50
- @apply border-input-border-disabled bg-input-surface-disabled;
51
- }
52
-
53
- .IncodePhoneCountryButton {
54
- @apply flex items-center justify-center w-full h-full;
55
- @apply p-8 pl-16;
56
- @apply bg-transparent border-none cursor-pointer;
57
- @apply text-18 font-medium text-text-body-primary;
58
- @apply rounded-small;
59
- font-family: inherit;
60
- }
61
-
62
- .IncodePhoneCountryButton:disabled {
63
- @apply cursor-not-allowed text-input-text-field-disabled;
64
- }
65
-
66
- .IncodePhoneCountryButton:focus {
67
- @apply outline-none;
68
- }
69
-
70
- .IncodePhoneCountryFlag {
71
- @apply text-24 leading-none;
72
- }
73
-
74
- .IncodePhoneCountryCode {
75
- @apply text-18 font-medium whitespace-nowrap text-text-body-primary ml-4;
76
- }
77
-
78
- .IncodePhoneCountryArrow {
79
- @apply w-24 h-24 text-icon-neutral-500 transition-transform duration-200 ml-8;
80
- }
81
-
82
- .IncodePhoneCountryArrowUp {
83
- @apply rotate-180;
84
- }
85
-
86
- .IncodePhoneCountryButton:disabled .IncodePhoneCountryArrow {
87
- @apply text-icon-neutral-300;
88
- }
89
-
90
- /* Country Dropdown */
91
- .IncodePhoneCountryDropdown {
92
- @apply absolute top-full left-0 right-0 z-50 mt-8;
93
- @apply max-h-[320px] overflow-y-auto;
94
- @apply bg-surface-neutral-0 border border-input-border-default rounded-small;
95
- @apply p-8;
96
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
97
- }
98
-
99
- .IncodePhoneCountryOption {
100
- @apply w-full flex items-center gap-12 py-12 px-12 cursor-pointer rounded-small;
101
- @apply text-16 font-medium text-text-body-primary;
102
- @apply bg-transparent border-none text-left;
103
- font-family: inherit;
104
- }
105
-
106
- .IncodePhoneCountryOption:hover {
107
- @apply bg-surface-neutral-100;
108
- }
109
-
110
- .IncodePhoneCountryOption:focus {
111
- @apply outline-none bg-surface-neutral-100;
112
- }
113
-
114
- .IncodePhoneCountryOptionSelected {
115
- @apply bg-surface-brand-50;
116
- }
117
-
118
- .IncodePhoneCountryOptionSelected:hover {
119
- @apply bg-surface-brand-50;
120
- }
121
-
122
- .IncodePhoneCountryOptionCode {
123
- @apply text-16 text-text-body-secondary min-w-[52px];
124
- }
125
-
126
- .IncodePhoneCountryOptionName {
127
- @apply text-16 text-text-body-primary flex-1 truncate;
128
- }
129
-
130
- /* Phone Input Field */
131
- .IncodePhoneInputField {
132
- @apply w-full py-16 px-16 text-18 font-medium border-none outline-none bg-transparent text-input-text-field-default;
133
- @apply rounded-small;
134
- }
135
-
136
- .IncodePhoneInputField::placeholder {
137
- @apply text-input-text-field-placeholder font-medium;
138
- }
139
-
140
- .IncodePhoneInputField:disabled {
141
- @apply cursor-not-allowed text-input-text-field-disabled;
142
- }
143
-
144
- .IncodePhoneInputError {
145
- @apply text-input-text-helper-negative text-14 text-center m-0 font-medium;
146
- }
147
-
148
- .IncodePhoneInputOptIn {
149
- @apply flex items-center gap-8 text-14 text-checkbox-text-default cursor-pointer;
150
- }
151
-
152
- .IncodePhoneInputOptIn input[type="checkbox"] {
153
- @apply w-[18px] h-[18px] cursor-pointer accent-checkbox-surface-selected rounded-checkbox;
154
- }
155
-
156
- .IncodePhoneInputOptIn span {
157
- @apply select-none font-medium;
158
- }
@@ -1,13 +0,0 @@
1
- @reference "../styles/tailwind.css";
2
-
3
- .IncodeCameraButton {
4
- @apply flex items-center justify-center;
5
-
6
- .IncodeCameraButtonPath {
7
- @apply fill-border-brand-500;
8
- }
9
-
10
- .IncodeCameraButtonBackground {
11
- @apply fill-surface-brand-50;
12
- }
13
- }