@stokr/components-library 3.0.46 → 3.0.48

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.
@@ -10,6 +10,7 @@ import stdin_default$3 from "./ResetCode.js";
10
10
  import { authenticationApi } from "../../api/authenticationApi.js";
11
11
  import { useNavigate } from "react-router-dom";
12
12
  import { getConfig, getPlatformURL } from "../../runtime-config.js";
13
+ import { isAccountLockedError, showAccountLockedModal } from "../../utils/show-account-locked-modal.js";
13
14
  import { navigateToHref } from "../../routing/navigate-app.js";
14
15
  const LoginWithOTP = ({ withBackground, useRelativePathForMenu = false }) => {
15
16
  const navigate = useNavigate();
@@ -95,6 +96,19 @@ const LoginWithOTP = ({ withBackground, useRelativePathForMenu = false }) => {
95
96
  clearPopupError();
96
97
  return;
97
98
  }
99
+ if (isAccountLockedError(error)) {
100
+ clearPopupError();
101
+ setIsModalOpen((prev) => ({
102
+ ...prev,
103
+ login: false
104
+ }));
105
+ await showAccountLockedModal();
106
+ setIsModalOpen((prev) => ({
107
+ ...prev,
108
+ login: true
109
+ }));
110
+ return;
111
+ }
98
112
  if (errorMessage) {
99
113
  handleSetPopupError("login", errorMessage);
100
114
  } else {
@@ -13,11 +13,14 @@ import { InfoIcon } from "../InfoIcon/InfoIcon.js";
13
13
  import { Modal } from "../Modal/Modal.js";
14
14
  import stdin_default$4 from "./Sucess2FA.js";
15
15
  import { ModalInner } from "../Modal/Modal.styles.js";
16
+ import { showProgress } from "../Modal/SuccessModal/SuccessModal.js";
17
+ const SUPPORT_URL = "https://support.stokr.io/";
16
18
  const Main2FAFlow = ({
17
19
  onRequiresRecentLoginError,
18
20
  open2faflow,
19
21
  onLoginAgainClick,
20
22
  openDisable2faflow,
23
+ allowDisable2FA = false,
21
24
  title = "SET UP YOUR LOG IN TWO FACTOR AUTHENTICATION",
22
25
  subtitle = "Protect your account with an additional layer of security to log in",
23
26
  showSwitch = true,
@@ -27,7 +30,7 @@ const Main2FAFlow = ({
27
30
  const { user, checkMfaEnrollment, userMfaEnrollment, generateTotpSecret } = useContext(AuthContext);
28
31
  const [isFlowopen, setIsFlowOpen] = useState({
29
32
  enable2fa: open2faflow || false,
30
- disable2fa: openDisable2faflow || false,
33
+ disable2fa: allowDisable2FA && (openDisable2faflow || false),
31
34
  requiresRecentLogin: false
32
35
  });
33
36
  const [is2FAEnabled, setis2FAEnabled] = useState(false);
@@ -81,6 +84,16 @@ const Main2FAFlow = ({
81
84
  [nextFlowId]: true
82
85
  }));
83
86
  };
87
+ const showDisable2faBlockedModal = () => showProgress({
88
+ title: "TWO-FACTOR AUTHENTICATION REQUIRED",
89
+ subtitle: /* @__PURE__ */ jsxs(Fragment, { children: [
90
+ "Under our security policy, two-factor authentication is required on all accounts and cannot be turned off. To reset your method, contact our",
91
+ " ",
92
+ /* @__PURE__ */ jsx("a", { href: SUPPORT_URL, target: "_blank", rel: "noreferrer", children: "support team" }),
93
+ "."
94
+ ] }),
95
+ maxWidth: "820px"
96
+ });
84
97
  const handleFlowClose = (flowId, completed = false) => {
85
98
  setIsFlowOpen((prev) => ({ ...prev, [flowId]: false }));
86
99
  if (onFlowClose) onFlowClose({ flow: flowId, completed });
@@ -99,7 +112,14 @@ const Main2FAFlow = ({
99
112
  };
100
113
  const flowActions = {
101
114
  openEnable2FA: () => switchOpenFlow("disable2fa", "enable2fa"),
102
- openDisable2FA: () => switchOpenFlow("enable2fa", "disable2fa"),
115
+ openDisable2FA: () => {
116
+ if (!is2FAEnabled) return;
117
+ if (!allowDisable2FA) {
118
+ void showDisable2faBlockedModal();
119
+ return;
120
+ }
121
+ switchOpenFlow("enable2fa", "disable2fa");
122
+ },
103
123
  closeFlows: () => {
104
124
  const wasOpen = Object.keys(isFlowopen).find((key) => isFlowopen[key]);
105
125
  setIsFlowOpen({
@@ -148,6 +168,10 @@ const Main2FAFlow = ({
148
168
  } else if (value === "enabled") {
149
169
  switchOpenFlow("disable2fa", "enable2fa");
150
170
  } else if (value === "disabled") {
171
+ if (!allowDisable2FA) {
172
+ void showDisable2faBlockedModal();
173
+ return;
174
+ }
151
175
  switchOpenFlow("enable2fa", "disable2fa");
152
176
  }
153
177
  }
@@ -177,7 +201,7 @@ const Main2FAFlow = ({
177
201
  onSuccess: onEnable2FASuccess
178
202
  }
179
203
  ),
180
- isFlowopen.disable2fa && /* @__PURE__ */ jsx(
204
+ allowDisable2FA && isFlowopen.disable2fa && /* @__PURE__ */ jsx(
181
205
  stdin_default$3,
182
206
  {
183
207
  showFlow: isFlowopen.disable2fa,
@@ -210,6 +234,7 @@ const Main2FAFlow = ({
210
234
  };
211
235
  Main2FAFlow.propTypes = {
212
236
  onRequiresRecentLoginError: PropTypes.func,
237
+ allowDisable2FA: PropTypes.bool,
213
238
  /** Called when any flow/modal is closed. Receives the flowId: 'enable2fa' | 'disable2fa' | 'requiresRecentLogin' */
214
239
  onFlowClose: PropTypes.func
215
240
  };
@@ -29,6 +29,7 @@ const defaultProject = {
29
29
  const NewVentureModal = (props) => {
30
30
  const navigate = useNavigate();
31
31
  const { user } = useContext(AuthContext);
32
+ const isLoggedIn = !!user?._id;
32
33
  const {
33
34
  title = "Register Your Interest",
34
35
  description = "Register your interest and learn more about this investment opportunity.",
@@ -66,7 +67,10 @@ const NewVentureModal = (props) => {
66
67
  salesChannel,
67
68
  customSuccessMessage
68
69
  });
69
- const validationSchema = Yup.object().shape({
70
+ const validationSchema = isLoggedIn ? Yup.object().shape({
71
+ email: Yup.string().nullable(),
72
+ name: Yup.string().nullable()
73
+ }) : Yup.object().shape({
70
74
  email: Yup.string().matches(emailRegex, "Oops, that's not a valid address").required("Oops, this can't be blank"),
71
75
  name: Yup.string().required("Oops, this can't be blank")
72
76
  });
@@ -112,6 +116,7 @@ const NewVentureModal = (props) => {
112
116
  ] }) : /* @__PURE__ */ jsx(
113
117
  Formik,
114
118
  {
119
+ enableReinitialize: true,
115
120
  initialValues: {
116
121
  email: email || "",
117
122
  name: name || "",
@@ -133,20 +138,6 @@ const NewVentureModal = (props) => {
133
138
  };
134
139
  const oneOfCheckbox = values.mailingList && !mailingListDisabled || values.privateInvestorList && !privateInvestorListDisabled;
135
140
  const optionalCheckBoxForm = optionalCheckBox ? values.optionalCheckBoxChecked : true;
136
- if (email && !values.email) {
137
- setFieldValue("email", email);
138
- setFieldTouched("email");
139
- }
140
- if (name && !values.name) {
141
- setFieldValue("name", name);
142
- setFieldTouched("name");
143
- }
144
- if (mailingList && !values.mailingList) {
145
- setFieldValue("mailingList", mailingList);
146
- }
147
- if (privateInvestorList && !values.privateInvestorList) {
148
- setFieldValue("privateInvestorList", privateInvestorList);
149
- }
150
141
  const handleCheckboxChange = (e) => {
151
142
  handleChange(e);
152
143
  if (e.target.checked) {
@@ -155,10 +146,11 @@ const NewVentureModal = (props) => {
155
146
  updateCheckedCheckboxes(checkedCheckboxes.filter((item) => item !== e.target.id));
156
147
  }
157
148
  };
158
- var submitDisabled = !touched.email || !!errors.email || !touched.name || !!errors.name || isSubmitting || !oneOfCheckbox || !optionalCheckBoxForm;
149
+ const requiresIdentityFields = !isLoggedIn;
150
+ const submitDisabled = requiresIdentityFields && (!values.email || !!errors.email || !values.name || !!errors.name) || isSubmitting || !oneOfCheckbox || !optionalCheckBoxForm;
159
151
  return /* @__PURE__ */ jsxs(stdin_default, { children: [
160
152
  /* @__PURE__ */ jsx(ComponentWrapper, { noPadding: true, children: /* @__PURE__ */ jsxs(FormField, { children: [
161
- !user?._id && /* @__PURE__ */ jsxs(Fragment, { children: [
153
+ !isLoggedIn && /* @__PURE__ */ jsxs(Fragment, { children: [
162
154
  /* @__PURE__ */ jsx(
163
155
  Input,
164
156
  {
@@ -23,6 +23,7 @@ import { auth } from "../../firebase-config.js";
23
23
  import { getConfig } from "../../runtime-config.js";
24
24
  import { getBackofficeAppUrl } from "../../utils/app-urls-analytics-backoffice.js";
25
25
  import { track } from "../../analytics/index.js";
26
+ import { isAccountLockedError, showAccountLockedModal } from "../../utils/show-account-locked-modal.js";
26
27
  import { navigateToHref } from "../../routing/navigate-app.js";
27
28
  const Outer = styled.div.withConfig({
28
29
  shouldForwardProp: (props) => !["fixed"].includes(props)
@@ -320,6 +321,15 @@ const _HeaderHoClass = class _HeaderHoClass extends Component {
320
321
  this.setIsActionLoading(void 0);
321
322
  return;
322
323
  }
324
+ if (isAccountLockedError(error)) {
325
+ this.setOpenModalStatus("login", false);
326
+ setIsLoginModalOpen?.(false);
327
+ this.clearPopupError();
328
+ this.setIsActionLoading(void 0);
329
+ await showAccountLockedModal();
330
+ this.setOpenModalStatus("login", true);
331
+ return;
332
+ }
323
333
  if (errorMessage) {
324
334
  this.setPopupError("login", errorMessage);
325
335
  } else {
@@ -75,6 +75,11 @@ const updateUserSubscription = (user, project, updates) => {
75
75
  }
76
76
  return { ...user, ...updates };
77
77
  };
78
+ const resolveUserDisplayName = (user) => {
79
+ if (!user) return "";
80
+ const fullNameFromParts = `${user.firstName || ""} ${user.lastName || ""}`.trim();
81
+ return user.username || user.name || user.fullName || fullNameFromParts || "";
82
+ };
78
83
  const useNewVentureForm = ({ project, user, salesChannel, customSuccessMessage }) => {
79
84
  const [formValues, setFormValues] = useState(initialState);
80
85
  const { setUser, checkPrivateInvestorAll, checkIfUserSubscribedAll } = useContext(AuthContext);
@@ -86,9 +91,9 @@ const useNewVentureForm = ({ project, user, salesChannel, customSuccessMessage }
86
91
  setFormValues((prev) => ({ ...prev, isSubmitting: true }));
87
92
  try {
88
93
  const dataToSend = {
89
- email: values.email,
94
+ email: values.email || user?.email || "",
90
95
  projectId: project._id,
91
- name: values.name,
96
+ name: values.name || resolveUserDisplayName(user),
92
97
  checkedCheckboxes: formValues.checkedCheckboxes,
93
98
  salesChannel: salesChannel ? salesChannel : window.location.pathname.includes("featured-assets") ? "featuredAssets" : "investor"
94
99
  };
@@ -192,8 +197,8 @@ const useNewVentureForm = ({ project, user, salesChannel, customSuccessMessage }
192
197
  });
193
198
  }
194
199
  if (user?._id) {
195
- formValuesCopy.email = user.email;
196
- formValuesCopy.name = user.username;
200
+ formValuesCopy.email = user.email || "";
201
+ formValuesCopy.name = resolveUserDisplayName(user);
197
202
  const projectSubscription = getProjectSubscription(user, project);
198
203
  if (projectSubscription.isPrivateInvestor) {
199
204
  formValuesCopy.privateInvestorListText = "You are already registered on the whitelist.";
package/dist/index.js CHANGED
@@ -135,6 +135,7 @@ import { isUSInvestor, usCountries } from "./utils/isUSInvestor.js";
135
135
  import { km_ify } from "./utils/km_ify.js";
136
136
  import { momentUtils } from "./utils/moment.js";
137
137
  import { openFile, saveAs } from "./utils/saveAs.js";
138
+ import { isAccountLockedError, showAccountLockedModal } from "./utils/show-account-locked-modal.js";
138
139
  import { ScrollToTop, scrollToElement, useScrollActions } from "./utils/scrollUtils.js";
139
140
  import { useTimer } from "./hooks/useTimer.js";
140
141
  import { useTransactionPolling } from "./hooks/useTransactionPolling.js";
@@ -625,6 +626,7 @@ export {
625
626
  iconsMap,
626
627
  identify,
627
628
  initAnalytics,
629
+ isAccountLockedError,
628
630
  isAlreadyOnOnboardingFlow,
629
631
  isExternalUrl,
630
632
  isUSInvestor,
@@ -647,6 +649,7 @@ export {
647
649
  scrollToElement,
648
650
  default6 as semanticUiStyle,
649
651
  setUserProperties,
652
+ showAccountLockedModal,
650
653
  showProgress,
651
654
  showSuccess,
652
655
  sizes,
@@ -0,0 +1,20 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import "react";
3
+ import { showProgress } from "../components/Modal/SuccessModal/SuccessModal.js";
4
+ const SUPPORT_URL = "https://support.stokr.io/";
5
+ const ACCOUNT_LOCKED_CODE = "auth/user-disabled";
6
+ const isAccountLockedError = (error) => error?.code === ACCOUNT_LOCKED_CODE;
7
+ const showAccountLockedModal = () => showProgress({
8
+ title: "TOO MANY ATTEMPTS",
9
+ subtitle: /* @__PURE__ */ jsxs(Fragment, { children: [
10
+ "For your security, your account has been temporarily locked after multiple failed sign-in attempts. To restore access, contact our",
11
+ " ",
12
+ /* @__PURE__ */ jsx("a", { href: SUPPORT_URL, target: "_blank", rel: "noreferrer", children: "support team" }),
13
+ "."
14
+ ] }),
15
+ maxWidth: "820px"
16
+ });
17
+ export {
18
+ isAccountLockedError,
19
+ showAccountLockedModal
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stokr/components-library",
3
- "version": "3.0.46",
3
+ "version": "3.0.48",
4
4
  "description": "STOKR - Components Library",
5
5
  "author": "Bilal Hodzic <bilal@stokr.io>",
6
6
  "license": "MIT",