@stokr/components-library 3.0.31 → 3.0.33

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 (40) hide show
  1. package/README.md +72 -287
  2. package/dist/analytics/index.js +2 -1
  3. package/dist/components/2FA/Connect2FA.js +11 -1
  4. package/dist/components/2FA/EnterCode.js +13 -2
  5. package/dist/components/2FA/InstallAuthApp.js +4 -0
  6. package/dist/components/2FA/ResetCode.js +2 -1
  7. package/dist/components/2FA/Sucess2FA.js +14 -2
  8. package/dist/components/2FA/disable-2fa-flow.js +15 -2
  9. package/dist/components/2FA/enable-2fa-flow.js +5 -0
  10. package/dist/components/2FA/login-with-otp-flow.js +2 -2
  11. package/dist/components/2FA/main-flow.js +3 -0
  12. package/dist/components/Button/GlareButton.js +273 -0
  13. package/dist/components/ConfirmModal/ConfirmModal.js +21 -2
  14. package/dist/components/FAQ/FAQ.js +37 -23
  15. package/dist/components/ForgotPasswordModal/ForgotPasswordModal.js +24 -3
  16. package/dist/components/Header/Header.js +1 -1
  17. package/dist/components/Input/InputPassword.js +27 -12
  18. package/dist/components/Input/OtpInput.js +21 -7
  19. package/dist/components/Input/Select.js +11 -2
  20. package/dist/components/Input/TableFilterDropdown.js +23 -7
  21. package/dist/components/Layout/Layout.js +6 -3
  22. package/dist/components/LoginModal/LoginModal.js +7 -4
  23. package/dist/components/Modal/Modal.styles.js +10 -2
  24. package/dist/components/Modal/NewVentureModal/NewVentureModal.js +21 -6
  25. package/dist/components/Modal/PaymentModal.js +16 -16
  26. package/dist/components/RegisterConfirmModal/RegisterConfirmModal.js +1 -1
  27. package/dist/components/RegisterModal/RegisterModal.js +30 -5
  28. package/dist/components/ResetConfirmModal/ResetConfirmModal.js +1 -1
  29. package/dist/components/ResetPasswordModal/ResetPasswordModal.js +16 -4
  30. package/dist/components/StepController/StepControllerProgress.js +1 -0
  31. package/dist/components/Switch/Switch.js +5 -3
  32. package/dist/components/ToDoList/ToDoListTask.js +1 -0
  33. package/dist/components/VerifyEmailModal/VerifyEmailModal.js +14 -2
  34. package/dist/components/headerHo/HeaderHo.js +2 -0
  35. package/dist/components/taxId/complete.js +23 -2
  36. package/dist/components/taxId/flow.js +11 -1
  37. package/dist/components/taxId/register-taxid.js +25 -4
  38. package/dist/index.js +2 -0
  39. package/dist/utils/formatCurrencyValue.js +4 -2
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -1,96 +1,43 @@
1
1
  # @stokr/components-library
2
2
 
3
- React component library for STOKR applications. Includes modals, forms, buttons, tables, and shared styles.
4
-
5
- ## Table of contents
6
-
7
- - [Installation](#installation)
8
- - [How to start](#how-to-start)
9
- - [Configuration](#configuration)
10
- - [Runtime config (npm consumers)](#runtime-config)
11
- - [URL variables and behavior](#url-variables-and-behavior)
12
- - [Reading config with `getConfig()`](#reading-config-with-getconfig)
13
- - [Ionicons](#ionicons)
14
- - [React Router](#react-router)
15
- - [Troubleshooting](#troubleshooting)
16
- - [Development & publishing](#development--publishing)
17
-
18
- ---
19
-
20
- ## Installation
21
-
22
- ```bash
23
- npm install @stokr/components-library
24
- ```
3
+ React UI library for STOKR apps: modals, forms, navigation, tables, auth context, styles.
25
4
 
26
- **Peer dependencies** (install in your app if not already present):
27
-
28
- ```bash
29
- npm install react react-dom styled-components react-router-dom
30
- ```
31
-
32
- - **React** 18 or 19
33
- - **styled-components** 6.x
34
- - **react-router-dom** 6.x (required if you use routing-dependent components)
35
-
36
- ---
5
+ ## Contents
37
6
 
38
- ## Implementing in your app
39
-
40
- Minimal path to use the library in a **Vite + React** consumer:
41
-
42
- 1. **Install** the package and [peer dependencies](#installation) (`react`, `react-dom`, `styled-components`, and `react-router-dom` if you use routing-heavy components).
43
- 2. **Router** — Wrap the tree with `BrowserRouter` (or another React Router v6 router) if you use `HeaderHo`, `MainMenu`, `LearnMore`, etc. If the host app may already provide a router (e.g. micro-frontend), use **`RouterWrapper`** from the package once at your root so you do not nest two `BrowserRouter` instances (see [step 2](#2-wrap-your-app-with-a-router-if-you-use-routing)).
44
- 3. **Runtime config** — Wrap your authenticated area with `<AuthProvider config={…}>` and pass **your** `import.meta.env.VITE_*` values (see [Runtime config](#runtime-config)). This is required for correct API URLs, Firebase, cookies, and domain-based links in pre-built npm installs.
45
- 4. **Optional** — `configure()` before `AuthProvider` if something (e.g. analytics) must read config earlier; `IoniconsStyles` or `styles.css` if you use icons or shared fonts (see below).
46
-
47
- Then import components from `@stokr/components-library` as in [step 4 under How to start](#4-import-and-use-components).
7
+ - [Quick start](#quick-start) install, router, `AuthProvider`, styles
8
+ - [Configuration reference](#runtime-config) — `config` keys & helpers
9
+ - [AuthProvider & AuthContext](#authprovider-optional-props)
10
+ - [Troubleshooting](#troubleshooting)
11
+ - [Development](#development--publishing)
48
12
 
49
13
  ---
50
14
 
51
- ## How to start
15
+ ## Quick start
52
16
 
53
- ### 1. Install the package and peers
17
+ **Install**
54
18
 
55
19
  ```bash
56
- npm install @stokr/components-library react react-dom styled-components react-router-dom
20
+ npm install @stokr/components-library
21
+ npm install react react-dom styled-components react-router-dom
57
22
  ```
58
23
 
59
- ### 2. Wrap your app with a Router (if you use routing)
60
-
61
- Components that use navigation (e.g. `MainMenu`, `LearnMore`, `HeaderHo`) must live inside a React Router.
24
+ Peers: React 18+/19+, styled-components 6.x, react-router-dom 6.x when you use routing-driven components (`HeaderHo`, `MainMenu`, …).
62
25
 
63
- **Simple app** — wrap with `BrowserRouter`:
26
+ **1. Router** — Navigation helpers need a React Router:
64
27
 
65
28
  ```jsx
66
- // main.jsx or App.jsx
67
29
  import { BrowserRouter } from 'react-router-dom'
68
- import App from './App'
69
-
70
- root.render(
71
- <BrowserRouter>
72
- <App />
73
- </BrowserRouter>,
74
- )
75
- ```
76
-
77
- **Host already has a router** (or you want one `BrowserRouter` only when needed) — wrap the library subtree with **`RouterWrapper`** from `@stokr/components-library`. It renders `BrowserRouter` only when `useInRouterContext()` is false, so you avoid duplicate routers and keep SPA navigation for `withRouter` / `useNavigate`:
78
-
79
- ```jsx
80
30
  import { RouterWrapper } from '@stokr/components-library'
81
- import { BrowserRouter } from 'react-router-dom'
82
- import App from './App'
83
31
 
84
- // Host owns the router:
32
+ // Normal app
85
33
  root.render(
86
34
  <BrowserRouter>
87
- <RouterWrapper>
88
- <App />
89
- </RouterWrapper>
35
+ <App />
90
36
  </BrowserRouter>,
91
37
  )
92
38
 
93
- // Standalone shell (no outer router): RouterWrapper adds BrowserRouter once.
39
+ // Outside a Router: RouterWrapper wraps children in BrowserRouter once.
40
+ // Inside an existing Router: it renders children only (no nested BrowserRouter).
94
41
  root.render(
95
42
  <RouterWrapper>
96
43
  <App />
@@ -98,53 +45,12 @@ root.render(
98
45
  )
99
46
  ```
100
47
 
101
- ### 3. (Optional) Add Ionicons for icons
102
-
103
- If you use **Modal**, **ConfirmModal**, **BackButton**, **Select**, **InfoIcon**, etc., add the icon styles once at the root:
104
-
105
- ```jsx
106
- import { IoniconsStyles } from '@stokr/components-library'
107
-
108
- function App() {
109
- return (
110
- <>
111
- <IoniconsStyles />
112
- {/* your app */}
113
- </>
114
- )
115
- }
116
- ```
117
-
118
- You can skip this; the library will inject icon styles when you first use a component that needs them.
119
-
120
- ### 4. Import and use components
121
-
122
- ```jsx
123
- import { ConfirmModal, Button } from '@stokr/components-library'
124
-
125
- function MyPage() {
126
- const [open, setOpen] = useState(false)
127
- return (
128
- <>
129
- <Button onClick={() => setOpen(true)}>Open</Button>
130
- <ConfirmModal isOpen={open} onClose={() => setOpen(false)} onConfirm={() => {}} title="Confirm?" />
131
- </>
132
- )
133
- }
134
- ```
135
-
136
- ---
137
-
138
- ## Configuration
139
-
140
- ### Runtime config (required when consuming as npm package) {#runtime-config}
141
-
142
- Since v3.0.16, the library uses a **runtime config** system. When this package is consumed by an external Vite app, `import.meta.env` values are baked at **library build time** and do not reflect the consuming app's `.env` file. Pass a `config` prop to `<AuthProvider>` so API URLs, Firebase credentials, and cookie domain are resolved from **your** environment:
48
+ **2. Auth & runtime config (required when consuming via npm)** Vite freezes `import.meta.env` inside pre-built deps, so push your `.env` at runtime via `config`:
143
49
 
144
50
  ```jsx
145
51
  import { AuthProvider } from '@stokr/components-library'
146
52
 
147
- function App() {
53
+ export default function App() {
148
54
  return (
149
55
  <AuthProvider
150
56
  config={{
@@ -167,215 +73,94 @@ function App() {
167
73
  measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
168
74
  },
169
75
  }}>
170
- {/* your app */}
76
+ {/* app */}
171
77
  </AuthProvider>
172
78
  )
173
79
  }
174
80
  ```
175
81
 
176
- | Prop key | Env variable it replaces | Purpose |
177
- | -------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
178
- | `apiUrl` | `VITE_API_URL` | Backend API base URL |
179
- | `baseUrlPublic` | `VITE_BASE_URL_PUBLIC` | Public (no-auth) API base URL |
180
- | `cookieDomain` | `VITE_COOKIE_DOMAIN` | Domain attribute for auth cookies |
181
- | `websiteDomain` | `VITE_WEBSITE_DOMAIN` | Platform domain (redirects, links) |
182
- | `photoApiUrl` | `VITE_PHOTO_API_URL` | Photo upload / avatar API URL |
183
- | `onboardingUrl` | `VITE_ONBOARDING_URL` | Full onboarding app URL (signup/signin flow) |
184
- | `dashboardUrl` | `VITE_DASHBOARD_URL` | Full investor dashboard URL |
185
- | `adminUrl` | `VITE_ADMIN_URL` | Full admin/venture dashboard URL |
186
- | `registerUrl` | `VITE_REGISTER_URL` | Public registration entry URL |
187
- | `firebase` | `VITE_FIREBASE_*` | Full Firebase config object |
188
-
189
- > **Why is this needed?** With the old CRA / `react-scripts` build, Webpack re-processed library code through the consuming app's build pipeline, so the app's `.env` values were injected automatically. Vite treats npm packages as pre-built — `import.meta.env` values in the compiled library are frozen at library build time. The `config` prop passes them at runtime instead.
190
-
191
- If you also need config values **before** `<AuthProvider>` mounts (e.g. for analytics init), you can call `configure()` directly:
192
-
193
- ```js
194
- import { configure } from '@stokr/components-library'
195
-
196
- configure({
197
- apiUrl: import.meta.env.VITE_API_URL,
198
- cookieDomain: import.meta.env.VITE_COOKIE_DOMAIN,
199
- // ...
200
- })
201
- ```
202
-
203
- After `configure()` / `<AuthProvider config>`, the library may log a **one-time `console.warn`** listing any **`VITE_*`** variables that are still missing (no override and no env fallback). Fix your `.env` or extend the `config` object until the list is empty.
82
+ **3. Earlier config** (e.g. analytics before mount): `import { configure } from '@stokr/components-library'` with the same shape as `config`.
204
83
 
205
- ### URL variables and behavior {#url-variables-and-behavior}
84
+ **4. Icons / fonts** Optional: `<IoniconsStyles />` at root, or rely on lazy injection when a component uses icons; for Layout / Open Sans: `import '@stokr/components-library/styles.css'`.
206
85
 
207
- The URL model is now explicit: pass full app URLs instead of routing mode/path segment rules.
86
+ **5. Imports** `import { ConfirmModal, Button, } from '@stokr/components-library'`.
208
87
 
209
- Expected URL env variables (matching current `.env.local`):
88
+ Full URL env example:
210
89
 
211
90
  ```bash
212
- VITE_WEBSITE_DOMAIN=stokr.info
213
- VITE_ONBOARDING_URL=https://signup.stokr.info
214
- VITE_DASHBOARD_URL=https://dashboard.stokr.info
215
- VITE_ADMIN_URL=https://admin.stokr.info
216
- VITE_REGISTER_URL=https://stokr.info/signup
91
+ VITE_WEBSITE_DOMAIN=example.com
92
+ VITE_ONBOARDING_URL=https://signup.example.com
93
+ VITE_DASHBOARD_URL=https://dashboard.example.com
94
+ VITE_ADMIN_URL=https://admin.example.com
95
+ VITE_REGISTER_URL=https://example.com/signup
217
96
  ```
218
97
 
219
- How each value is used:
220
-
221
- - `VITE_ONBOARDING_URL`: signup/signin flow redirects (for example `/welcome`, `/resend-activation-email`).
222
- - `VITE_DASHBOARD_URL`: investor dashboard entry point.
223
- - `VITE_ADMIN_URL`: admin/venture dashboard entry point.
224
- - `VITE_REGISTER_URL`: register CTA/entry URL.
225
- - `VITE_WEBSITE_DOMAIN`: base host used by `getPlatformURL()` and fixed subdomains (analytics/backoffice).
226
-
227
- Derived helpers:
228
-
229
- - `getPlatformURL()` => `https://{VITE_WEBSITE_DOMAIN}` (for example `https://stokr.info`).
230
- - `getAnalyticsIngestUrl()` => `https://analytics.{VITE_WEBSITE_DOMAIN}`.
231
- - `getBackofficeAppUrl('/path')` => `https://backoffice.{VITE_WEBSITE_DOMAIN}/path`.
232
-
233
- **Authentication-only HTTP** — backend routes under `auth/*` (e.g. forgot password) are exposed as **`authenticationApi.post(segment, body)`** from `src/api/authenticationApi.js` (also re-exported from the package entry). The default axios instance is the **general API client** for all authenticated backend calls, not auth-only.
234
-
235
- ### Reading config with `getConfig()` {#reading-config-with-getconfig}
236
-
237
- In your app (or in code next to the library), read the same resolved values the package uses:
238
-
239
- ```js
240
- import { getConfig } from '@stokr/components-library'
241
-
242
- const api = getConfig('apiUrl')
243
- const domain = getConfig('websiteDomain')
244
- const firebaseOptions = getConfig('firebase') // override object or env-built fallback
245
- const onboarding = getConfig('onboardingUrl')
246
- const dashboard = getConfig('dashboardUrl')
247
- const admin = getConfig('adminUrl')
248
- const register = getConfig('registerUrl')
249
- ```
250
-
251
- Supported keys: `apiUrl`, `baseUrlPublic`, `cookieDomain`, `websiteDomain`, `photoApiUrl`, `onboardingUrl`, `dashboardUrl`, `adminUrl`, `registerUrl`, `firebase`. Resolution order is always **explicit `configure()` / `AuthProvider` `config`** first, then **`import.meta.env`** in the consuming Vite app (where a `VITE_*` mapping exists).
252
-
253
- **Marketing site origin** — the old `platformDomain` / `platformURL` exports were removed. Use **`getPlatformURL()`** for `https://{websiteDomain}`, or **`getConfig('websiteDomain')`** for the bare host (e.g. `"example.com"`). Both read the same runtime config after **`configure()`** / **`AuthProvider`**.
254
-
255
- ```js
256
- import { getConfig, getPlatformURL } from '@stokr/components-library'
257
-
258
- const host = getConfig('websiteDomain') // e.g. "example.com"
259
- const origin = getPlatformURL() // e.g. "https://example.com"
260
- ```
261
-
262
- **Footer link groups** — if you previously imported the static `footerGroups` from `FooterLayout`, import **`getFooterGroups`** instead (same package entry as other footer exports). It returns fresh URLs using the current `getPlatformURL()`.
263
-
264
- ### Ionicons
265
-
266
- Components such as **Modal**, **ConfirmModal**, **BackButton**, **InfoIcon**, **Select**, **MainMenu**, and **RegisterLiquidSteps** use [Ionicons](http://ionicons.com/). You can enable them in three ways:
267
-
268
- | Approach | When to use |
269
- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
270
- | **Global injection** | Render `<IoniconsStyles />` once at app root. Full icon set, singleton. |
271
- | **No setup** | Don’t render anything; styles inject on first use of an icon component. |
272
- | **CSS import** | Prefer loading via CSS: `import '@stokr/components-library/styles.css'` or `import '@stokr/components-library/ionicons.css'`. |
273
-
274
- **Global injection example:**
275
-
276
- ```jsx
277
- import { IoniconsStyles } from '@stokr/components-library'
278
-
279
- function App() {
280
- return (
281
- <>
282
- <IoniconsStyles />
283
- {/* your routes, layout, etc. */}
284
- </>
285
- )
286
- }
287
- ```
288
-
289
- If you use **Layout**, **GlobalStyle**, or need **Open Sans**, import the full styles once:
290
-
291
- ```js
292
- import '@stokr/components-library/styles.css'
293
- ```
294
-
295
- ### React Router
296
-
297
- Any component that uses `useNavigate()` or routing must be rendered inside a `Router` from `react-router-dom` (e.g. `BrowserRouter`), or under **`RouterWrapper`** so a router exists when the host does not provide one. Components wrapped with **`withRouter`** receive `navigate` from `useNavigate()` when inside a Router; outside a Router, `navigate` throws with a message to add `BrowserRouter` or `RouterWrapper`. See [How to start – step 2](#2-wrap-your-app-with-a-router-if-you-use-routing).
298
-
299
98
  ---
300
99
 
301
- ## Troubleshooting
100
+ ## Configuration reference {#runtime-config}
302
101
 
303
- ### "useNavigate() may be used only in the context of a \<Router\> component"
102
+ | `config` key | Typical `VITE_*` | Role |
103
+ | ----------------- | -------------------------- | ----------------------------------------- |
104
+ | `apiUrl` | `VITE_API_URL` | Authenticated REST base |
105
+ | `baseUrlPublic` | `VITE_BASE_URL_PUBLIC` | Public API base |
106
+ | `cookieDomain` | `VITE_COOKIE_DOMAIN` | Auth cookie `domain` attribute |
107
+ | `websiteDomain` | `VITE_WEBSITE_DOMAIN` | Bare host → links / `getPlatformURL()` |
108
+ | `photoApiUrl` | `VITE_PHOTO_API_URL` | Avatars / media |
109
+ | `onboardingUrl` | `VITE_ONBOARDING_URL` | Full signup/sign-in app URL |
110
+ | `dashboardUrl` | `VITE_DASHBOARD_URL` | Investor dashboard URL |
111
+ | `adminUrl` | `VITE_ADMIN_URL` | Venture / admin dashboard URL |
112
+ | `registerUrl` | `VITE_REGISTER_URL` | Public registration entry |
113
+ | `firebase` | `VITE_FIREBASE_*` | Firebase client config |
304
114
 
305
- Wrap your app with a Router, or use **`RouterWrapper`** at the root when you are not already inside a host `BrowserRouter`:
115
+ **Helpers:** `getConfig('…')` for any key above; `getPlatformURL()` `https://{websiteDomain}`; `getAnalyticsIngestUrl()`, `getBackofficeAppUrl(path)`; **`getFooterGroups()`** replaces static footer URL lists.
306
116
 
307
- ```jsx
308
- import { BrowserRouter } from 'react-router-dom'
117
+ **Auth-only HTTP:** `authenticationApi.post(segment, body)` for `auth/*`; default axios stays the main API client after login.
309
118
 
310
- root.render(
311
- <BrowserRouter>
312
- <App />
313
- </BrowserRouter>,
314
- )
315
- ```
119
+ Missing overrides may trigger a **one-time** warning listing unresolved `VITE_*` expectations.
316
120
 
317
- ```jsx
318
- import { RouterWrapper } from '@stokr/components-library'
121
+ ---
319
122
 
320
- root.render(
321
- <RouterWrapper>
322
- <App />
323
- </RouterWrapper>,
324
- )
325
- ```
123
+ ## AuthProvider & AuthContext {#authprovider-optional-props}
326
124
 
327
- Install the peer dependency: `npm install react-router-dom`
125
+ `AuthProvider` is wrapped with **`withRouter`** mount it inside a **`Router`** so redirects work.
328
126
 
329
- ### "Invalid hook call" / "Cannot read properties of null (reading 'use')"
127
+ ### Optional provider props
330
128
 
331
- Your app and the library must use the **same** React instance.
129
+ - **`inactivityTimeMs`** Idle timeout before auto-logout + session modal (default 5 min).
130
+ - **`accessTokenExpiryMs`** — Cookie TTL when **`Auth.setAccessToken`** runs (default **`DEFAULT_TOKEN_EXPIRY_MS`** = 1 h); not extended by **`getUser`** alone.
131
+ - **`hideInactivityModal`** — Suppress built-in session modal.
132
+ - **`customValidateGetUser(user)`** — Hook after **`user/get`** succeeds (see `src/context/AuthContext.js`).
332
133
 
333
- 1. Install peer dependencies:
334
- `npm install react react-dom styled-components`
134
+ ### AuthContext consumer {#authcontext-usecontext}
335
135
 
336
- 2. **Vite apps** add dedupe in `vite.config.js` or `vite.config.ts`:
136
+ `import { AuthContext } from '…'` then `useContext(AuthContext)`.
337
137
 
338
- ```js
339
- export default defineConfig({
340
- resolve: {
341
- dedupe: ['react', 'react-dom', 'styled-components'],
342
- },
343
- // ...rest of config
344
- })
345
- ```
138
+ - **Invalid Firebase guard:** value is **`{ user: null, isFetchingUser: false }`** only (no methods).
139
+ - **State (grouped):** `user` / `firebaseUser`, `isFetchingUser`, `avatar`; MFA (`waitingFor2fa`, `userMfaEnrollment`, `firebaseError`); verify-email (`verifyEmailError`, `isVerifyingEmail`); session UX (`loggedOutDueToInactivity`, `loggedOutDueToCookieExpiry`, `sessionExpiryPendingReason`).
140
+ - **Actions (grouped):** `userRef`, `loginUser`, `logoutUser`, `getUser`, `setUser`, `updateUser`, `refreshIdToken`; `checkUserIsValid`, `checkTokenIsValid`; `uploadPhoto`, `deletePhoto`, `checkUserPhoto`; MFA enroll / verify / unenroll + `reset2faFlow`; subscription/onboarding helpers; password & email (`handleResetPassword`, `handleVerifyEmail`, `requestUpdateEmail`, …); wallets / PoA (`uploaProofOfAddress` keeps the source spelling); **`dismissSessionExpiryModal`**.
346
141
 
347
- 3. Reinstall or refresh the library after updating (e.g. `npm install` or clear cache and reinstall).
142
+ Use **`AuthConsumer`** for the render-prop pattern.
348
143
 
349
144
  ---
350
145
 
351
- ## Development & publishing
352
-
353
- ### Run Storybook
146
+ ## Troubleshooting {#troubleshooting}
354
147
 
355
- ```bash
356
- npm run storybook
357
- ```
148
+ **`useNavigate` / Router**
358
149
 
359
- Use **Story Source** for consumption examples and **Viewport** for different screen sizes.
150
+ Add `BrowserRouter` (or **`RouterWrapper`** from this package where the shell has no router). Install `react-router-dom`.
360
151
 
361
- ### Build for distribution
152
+ **Invalid hook call / duplicated React**
362
153
 
363
- ```bash
364
- npm run build:dist
365
- ```
154
+ Dedupe peers in Vite: `resolve: { dedupe: ['react', 'react-dom', 'styled-components'] }`.
366
155
 
367
- This runs `vite build` and copies static assets to `dist/`.
368
-
369
- ### Publish a new version
370
-
371
- 1. Commit your changes.
372
- 2. Update [CHANGELOG.md](CHANGELOG.md) – add a new `# vX.Y.Z` section at the top with the list of changes.
373
- 3. Bump the version: `npm version <version>` (e.g. `npm version 3.0.7`).
374
- 4. (ensure you are authenticated) Run `npm login` to log first on NPM package (only needed one time a day)
375
- 5. Run `npm run pub` (will first run `npm run build:dist`).
156
+ ---
376
157
 
377
- Consumers can see what changed in each release in [CHANGELOG.md](CHANGELOG.md).
158
+ ## Development {#development--publishing}
378
159
 
379
- ### Reference
160
+ | Command | Meaning |
161
+ | -------------------- | -------------------------------- |
162
+ | `npm run storybook` | Local docs / stories |
163
+ | `npm run build:dist` | Library build + static copy |
164
+ | `npm run pub` | Build then publish (`npm login`) |
380
165
 
381
- - [How to publish a React component library](https://medium.com/better-programming/how-to-publish-a-react-component-library-c89a07566770)
166
+ Release: changelog entry `npm version` `npm run pub`. Details: [CHANGELOG.md](CHANGELOG.md).
@@ -95,7 +95,8 @@ function initAnalytics({
95
95
  rage_click: true,
96
96
  scroll,
97
97
  submit: true,
98
- capture_text_content: false
98
+ capture_text_content: false,
99
+ capture_extra_attrs: ["data-cy"]
99
100
  },
100
101
  record_sessions_percent: 100,
101
102
  record_heatmap_data: true,
@@ -44,7 +44,17 @@ const Connect2FA = (props) => {
44
44
  }
45
45
  )
46
46
  ] }),
47
- /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingTop: true, center: true, noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(Button, { minWidth: "150px", onClick: changeStep, fluid: true, children: "Continue" }) })
47
+ /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingTop: true, center: true, noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(
48
+ Button,
49
+ {
50
+ minWidth: "150px",
51
+ id: "enable-2fa-connect-continue-btn",
52
+ "data-cy": "enable-2fa-connect-continue",
53
+ onClick: changeStep,
54
+ fluid: true,
55
+ children: "Continue"
56
+ }
57
+ ) })
48
58
  ] }) })
49
59
  ] }) });
50
60
  };
@@ -19,7 +19,7 @@ const EnterCode = (props) => {
19
19
  /* @__PURE__ */ jsx("h3", { children: "Enter 2FA code" }),
20
20
  /* @__PURE__ */ jsx("p", { children: "Enter the log in 2FA code from your authenticator app" })
21
21
  ] }) }),
22
- /* @__PURE__ */ jsx(ModalInner, { modalBot: true, children: onModalSwitch && /* @__PURE__ */ jsx(ModalLinkWrap, { children: /* @__PURE__ */ jsx(ModalLink, { type: "button", as: "button", onClick: onModalSwitch, children: "Lost your device?" }) }) })
22
+ /* @__PURE__ */ jsx(ModalInner, { modalBot: true, children: onModalSwitch && /* @__PURE__ */ jsx(ModalLinkWrap, { children: /* @__PURE__ */ jsx(ModalLink, { type: "button", as: "button", "data-cy": "2fa-enter-code-lost-device", onClick: onModalSwitch, children: "Lost your device?" }) }) })
23
23
  ] }),
24
24
  /* @__PURE__ */ jsx(Column, { part: 8, children: /* @__PURE__ */ jsx(ModalInner, { children: /* @__PURE__ */ jsx(
25
25
  Formik,
@@ -38,6 +38,7 @@ const EnterCode = (props) => {
38
38
  id: "otp-input",
39
39
  name: "otpInput",
40
40
  numInputs: 6,
41
+ dataCyPrefix: "2fa-enter-code-otp",
41
42
  value: values.otpInput,
42
43
  onChange: (e) => {
43
44
  setFieldValue("otpInput", e);
@@ -48,7 +49,17 @@ const EnterCode = (props) => {
48
49
  ),
49
50
  /* @__PURE__ */ jsx(FormError, { show: errors.otpInput && touched.otpInput, children: errors.otpInput })
50
51
  ] }) }),
51
- /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingBottom: true, noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(Button, { type: "submit", id: "2fa-enter-code-btn", fluid: true, disabled: submitDisabled, children: "Continue" }) }),
52
+ /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingBottom: true, noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(
53
+ Button,
54
+ {
55
+ type: "submit",
56
+ id: "2fa-enter-code-btn",
57
+ "data-cy": "2fa-enter-code-submit",
58
+ fluid: true,
59
+ disabled: submitDisabled,
60
+ children: "Continue"
61
+ }
62
+ ) }),
52
63
  /* @__PURE__ */ jsx(ComponentWrapper, { paddingVeticalHalf: true, noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(FormError, { show: popupError?.popup === "enter2fa", children: popupError.message }) })
53
64
  ] });
54
65
  }
@@ -22,6 +22,8 @@ const InstallAuthApp = (props) => {
22
22
  Button,
23
23
  {
24
24
  minWidth: "150px",
25
+ id: "enable-2fa-download-app-btn",
26
+ "data-cy": "enable-2fa-download-auth-app",
25
27
  fluid: true,
26
28
  onClick: () => {
27
29
  window.open("https://onelink.to/ev63j9", "_blank");
@@ -50,6 +52,8 @@ const InstallAuthApp = (props) => {
50
52
  Button,
51
53
  {
52
54
  minWidth: "150px",
55
+ id: "enable-2fa-install-app-continue-btn",
56
+ "data-cy": "enable-2fa-install-app-continue",
53
57
  onClick: changeStep,
54
58
  fluid: true,
55
59
  children: "Continue"
@@ -12,7 +12,7 @@ const ResetCode = (props) => {
12
12
  /* @__PURE__ */ jsx("h3", { children: "Lost your device?" }),
13
13
  /* @__PURE__ */ jsx("p", { children: "No problem! We are here to help you..." })
14
14
  ] }) }),
15
- /* @__PURE__ */ jsx(ModalInner, { modalBot: true, children: /* @__PURE__ */ jsx(ModalLinkWrap, { children: /* @__PURE__ */ jsx(ModalLink, { type: "button", as: "button", onClick: onModalSwitch, children: "Enter 2FA code" }) }) })
15
+ /* @__PURE__ */ jsx(ModalInner, { modalBot: true, children: /* @__PURE__ */ jsx(ModalLinkWrap, { children: /* @__PURE__ */ jsx(ModalLink, { type: "button", as: "button", "data-cy": "2fa-reset-code-back-to-enter-code", onClick: onModalSwitch, children: "Enter 2FA code" }) }) })
16
16
  ] }),
17
17
  /* @__PURE__ */ jsx(Column, { part: 8, children: /* @__PURE__ */ jsx(ModalInner, { children: /* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(
18
18
  "a",
@@ -20,6 +20,7 @@ const ResetCode = (props) => {
20
20
  href: "https://stokr.zendesk.com/hc/en-us/requests/new",
21
21
  target: "_blank",
22
22
  rel: "noreferrer",
23
+ "data-cy": "2fa-reset-code-contact-support",
23
24
  style: { textDecoration: "underline" },
24
25
  children: "Contact us to reset your 2FA."
25
26
  }
@@ -12,7 +12,11 @@ const Sucess2FA = ({
12
12
  titleText = "Success!",
13
13
  subTitleLeft = "",
14
14
  textRight = "",
15
- buttonText = "Continue"
15
+ buttonText = "Continue",
16
+ /** DOM id for the primary action button — override per screen (verify email, enable/disable 2FA, etc.) */
17
+ continueButtonId = "success-flow-continue-btn",
18
+ /** Playwright / analytics-friendly selector for the primary button */
19
+ continueButtonDataCy = "success-flow-continue"
16
20
  }) => {
17
21
  return /* @__PURE__ */ jsx(ModalWrapper, { children: /* @__PURE__ */ jsxs(Row, { children: [
18
22
  /* @__PURE__ */ jsxs(Column, { part: 8, children: [
@@ -30,7 +34,9 @@ const Sucess2FA = ({
30
34
  /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(
31
35
  Button,
32
36
  {
37
+ id: continueButtonId,
33
38
  minWidth: "240px",
39
+ "data-cy": continueButtonDataCy,
34
40
  onClick: () => {
35
41
  onClick();
36
42
  },
@@ -42,7 +48,13 @@ const Sucess2FA = ({
42
48
  ] }) });
43
49
  };
44
50
  Sucess2FA.propTypes = {
45
- onClick: PropTypes.func.isRequired
51
+ onClick: PropTypes.func.isRequired,
52
+ titleText: PropTypes.string,
53
+ subTitleLeft: PropTypes.string,
54
+ textRight: PropTypes.string,
55
+ buttonText: PropTypes.string,
56
+ continueButtonId: PropTypes.string,
57
+ continueButtonDataCy: PropTypes.string
46
58
  };
47
59
  var stdin_default = Sucess2FA;
48
60
  export {
@@ -60,6 +60,8 @@ const Disable2FA = ({ showFlow, setShowFlow, onSuccess, onRequiresRecentLoginErr
60
60
  children: /* @__PURE__ */ jsx(ModalInner, { noPadding: true, children: isModalOpen.sucess ? /* @__PURE__ */ jsx(
61
61
  stdin_default$1,
62
62
  {
63
+ continueButtonId: "disable-2fa-success-continue-btn",
64
+ continueButtonDataCy: "disable-2fa-success-continue",
63
65
  onClick: () => {
64
66
  setShowFlow(false);
65
67
  onSuccess?.();
@@ -77,8 +79,19 @@ const Disable2FA = ({ showFlow, setShowFlow, onSuccess, onRequiresRecentLoginErr
77
79
  ] }),
78
80
  /* @__PURE__ */ jsx(Column, { part: 8, children: /* @__PURE__ */ jsxs(ModalInner, { children: [
79
81
  /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingVertical: true, noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx("p", { children: "Do you want to remove your log in 2FA authentication?" }) }) }),
80
- /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(Button, { secondary: true, style: { marginRight: 20 }, onClick: () => setShowFlow(false), fluid: true, children: "NO" }) }),
81
- /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingHorizontal: true, noPaddingTop: true, children: /* @__PURE__ */ jsx(Button, { onClick: handleDisableClick, fluid: true, children: "Yes" }) })
82
+ /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingHorizontal: true, children: /* @__PURE__ */ jsx(
83
+ Button,
84
+ {
85
+ secondary: true,
86
+ style: { marginRight: 20 },
87
+ id: "disable-2fa-confirm-no-btn",
88
+ "data-cy": "disable-2fa-confirm-no",
89
+ onClick: () => setShowFlow(false),
90
+ fluid: true,
91
+ children: "NO"
92
+ }
93
+ ) }),
94
+ /* @__PURE__ */ jsx(ComponentWrapper, { noPaddingHorizontal: true, noPaddingTop: true, children: /* @__PURE__ */ jsx(Button, { id: "disable-2fa-confirm-yes-btn", "data-cy": "disable-2fa-confirm-yes", onClick: handleDisableClick, fluid: true, children: "Yes" }) })
82
95
  ] }) })
83
96
  ] }) }) })
84
97
  }
@@ -64,6 +64,8 @@ const Enable2FAFlow = ({ showFlow, setShowFlow, onSuccess, totpData, onRequiresR
64
64
  return /* @__PURE__ */ jsx(Modal, { isOpen: showFlow, onClose: () => setShowFlow(false), children: /* @__PURE__ */ jsx(ModalInner, { noPadding: true, children: showSuccess ? /* @__PURE__ */ jsx(
65
65
  stdin_default$1,
66
66
  {
67
+ continueButtonId: "enable-2fa-success-continue-btn",
68
+ continueButtonDataCy: "enable-2fa-success-continue",
67
69
  onClick: () => {
68
70
  if (onSuccess) onSuccess();
69
71
  setshowSuccess(false);
@@ -88,18 +90,21 @@ const Enable2FAFlow = ({ showFlow, setShowFlow, onSuccess, totpData, onRequiresR
88
90
  stepsProgressList: [
89
91
  {
90
92
  id: "app",
93
+ dataCy: "enable-2fa-progress-app",
91
94
  handleClick: () => {
92
95
  stepController.changeStep("app", 0);
93
96
  }
94
97
  },
95
98
  {
96
99
  id: "connect",
100
+ dataCy: "enable-2fa-progress-connect",
97
101
  handleClick: () => {
98
102
  stepController.changeStep("connect", 1);
99
103
  }
100
104
  },
101
105
  {
102
106
  id: "enter-code",
107
+ dataCy: "enable-2fa-progress-enter-code",
103
108
  handleClick: () => stepController.changeStep("enter-code", 2)
104
109
  }
105
110
  ],