@memori.ai/memori-react 6.4.6 → 6.5.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 (62) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/components/AccountForm/AccountForm.d.ts +9 -0
  3. package/dist/components/AccountForm/AccountForm.js +122 -0
  4. package/dist/components/AccountForm/AccountForm.js.map +1 -0
  5. package/dist/components/LoginDrawer/LoginDrawer.css +59 -3
  6. package/dist/components/LoginDrawer/LoginDrawer.d.ts +4 -2
  7. package/dist/components/LoginDrawer/LoginDrawer.js +110 -25
  8. package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
  9. package/dist/components/SignupForm/SignupForm.d.ts +10 -0
  10. package/dist/components/SignupForm/SignupForm.js +201 -0
  11. package/dist/components/SignupForm/SignupForm.js.map +1 -0
  12. package/dist/components/VenueWidget/VenueWidget.css +14 -0
  13. package/dist/components/ui/Checkbox.css +2 -4
  14. package/dist/components/ui/Details.css +66 -0
  15. package/dist/helpers/error.js +4 -0
  16. package/dist/helpers/error.js.map +1 -1
  17. package/dist/helpers/utils.js +3 -3
  18. package/dist/helpers/utils.js.map +1 -1
  19. package/dist/locales/en.json +29 -0
  20. package/dist/locales/it.json +31 -2
  21. package/dist/styles.css +5 -3
  22. package/esm/components/AccountForm/AccountForm.d.ts +9 -0
  23. package/esm/components/AccountForm/AccountForm.js +119 -0
  24. package/esm/components/AccountForm/AccountForm.js.map +1 -0
  25. package/esm/components/LoginDrawer/LoginDrawer.css +59 -3
  26. package/esm/components/LoginDrawer/LoginDrawer.d.ts +4 -2
  27. package/esm/components/LoginDrawer/LoginDrawer.js +110 -24
  28. package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
  29. package/esm/components/SignupForm/SignupForm.d.ts +10 -0
  30. package/esm/components/SignupForm/SignupForm.js +198 -0
  31. package/esm/components/SignupForm/SignupForm.js.map +1 -0
  32. package/esm/components/VenueWidget/VenueWidget.css +14 -0
  33. package/esm/components/ui/Checkbox.css +2 -4
  34. package/esm/components/ui/Details.css +66 -0
  35. package/esm/helpers/error.js +4 -0
  36. package/esm/helpers/error.js.map +1 -1
  37. package/esm/helpers/utils.js +3 -3
  38. package/esm/helpers/utils.js.map +1 -1
  39. package/esm/locales/en.json +29 -0
  40. package/esm/locales/it.json +31 -2
  41. package/esm/styles.css +5 -3
  42. package/package.json +1 -1
  43. package/src/components/AccountForm/AccountForm.test.tsx +26 -0
  44. package/src/components/AccountForm/AccountForm.tsx +326 -0
  45. package/src/components/AccountForm/__snapshots__/AccountForm.test.tsx.snap +202 -0
  46. package/src/components/LoginDrawer/LoginDrawer.css +59 -3
  47. package/src/components/LoginDrawer/LoginDrawer.stories.tsx +19 -0
  48. package/src/components/LoginDrawer/LoginDrawer.test.tsx +48 -2
  49. package/src/components/LoginDrawer/LoginDrawer.tsx +233 -25
  50. package/src/components/LoginDrawer/__snapshots__/LoginDrawer.test.tsx.snap +24 -0
  51. package/src/components/SignupForm/SignupForm.test.tsx +39 -0
  52. package/src/components/SignupForm/SignupForm.tsx +458 -0
  53. package/src/components/SignupForm/__snapshots__/SignupForm.test.tsx.snap +247 -0
  54. package/src/components/VenueWidget/VenueWidget.css +14 -0
  55. package/src/components/ui/Checkbox.css +2 -4
  56. package/src/components/ui/Details.css +66 -0
  57. package/src/helpers/error.ts +4 -0
  58. package/src/helpers/utils.ts +4 -3
  59. package/src/locales/en.json +29 -0
  60. package/src/locales/it.json +32 -3
  61. package/src/mocks/data.ts +1 -0
  62. package/src/styles.css +5 -3
@@ -7,8 +7,9 @@ import { useTranslation } from 'react-i18next';
7
7
  import cx from 'classnames';
8
8
  import memoriApiClient from '@memori.ai/memori-api-client';
9
9
  import { getErrori18nKey } from '../../helpers/error';
10
-
11
- export const mailRegEx = /^\w+([.-]?[+]?\w+)*@\w+([.-]?\w+)*(\.\w{2,})+$/;
10
+ import { mailRegEx, pwdRegEx } from '../../helpers/utils';
11
+ import SignupForm from '../SignupForm/SignupForm';
12
+ import AccountForm from '../AccountForm/AccountForm';
12
13
 
13
14
  export interface Props {
14
15
  open?: boolean;
@@ -19,7 +20,10 @@ export interface Props {
19
20
  onLogout: () => void;
20
21
  tenant: Tenant;
21
22
  apiUrl: string;
23
+ __TEST__signup?: boolean;
22
24
  __TEST__needMissingData?: boolean;
25
+ __TEST__waitingForOtp?: boolean;
26
+ __TEST__changePwd?: boolean;
23
27
  }
24
28
 
25
29
  const LoginDrawer = ({
@@ -31,7 +35,10 @@ const LoginDrawer = ({
31
35
  loginToken,
32
36
  tenant,
33
37
  apiUrl,
38
+ __TEST__signup = false,
34
39
  __TEST__needMissingData = false,
40
+ __TEST__waitingForOtp = false,
41
+ __TEST__changePwd = false,
35
42
  }: Props) => {
36
43
  const { t, i18n } = useTranslation();
37
44
  const lang = i18n.language === 'it' ? 'it' : 'en';
@@ -39,7 +46,23 @@ const LoginDrawer = ({
39
46
  const client = memoriApiClient(apiUrl);
40
47
  const { userLogin, updateUser } = client.backend;
41
48
 
49
+ const isUserLoggedIn = user?.userID && loginToken;
50
+
42
51
  const [loading, setLoading] = useState(false);
52
+ const [error, setError] = useState<string | null>(null);
53
+
54
+ const [showSignup, setShowSignup] = useState(__TEST__signup);
55
+ const [userMustChangePwd, setUserMustChangePwd] = useState<User | undefined>(
56
+ __TEST__changePwd
57
+ ? {
58
+ flowID: 'flowID',
59
+ tenant: tenant.id,
60
+ eMail: 'email',
61
+ userName: 'username',
62
+ password: 'password',
63
+ }
64
+ : undefined
65
+ );
43
66
  const [needsMissingData, setNeedsMissingData] = useState<{
44
67
  token: string;
45
68
  birthDate?: boolean;
@@ -53,14 +76,6 @@ const LoginDrawer = ({
53
76
  }
54
77
  : ({} as any)
55
78
  );
56
- const [error, setError] = useState<string | null>(null);
57
-
58
- const [redirectTo, setRedirectTo] = useState<string | null>(null);
59
- useEffect(() => {
60
- setRedirectTo(window.location.href);
61
- }, []);
62
-
63
- const isUserLoggedIn = user?.userID && loginToken;
64
79
 
65
80
  const login = (e: React.FormEvent<HTMLFormElement>) => {
66
81
  e.preventDefault();
@@ -84,27 +99,37 @@ const LoginDrawer = ({
84
99
  setError(null);
85
100
  userLogin(user)
86
101
  .then(data => {
87
- if (data.resultCode !== 0) {
102
+ if (data.resultCode === -14) {
103
+ setUserMustChangePwd({
104
+ tenant: tenant.id,
105
+ eMail: isEmail ? userNameOrEmail : undefined,
106
+ userName: isEmail ? undefined : userNameOrEmail,
107
+ password: password,
108
+ flowID: (data.flowID || data.user?.flowID) as string,
109
+ });
110
+ } else if (data.resultCode !== 0) {
88
111
  console.error(data);
89
- toast.error(t(getErrori18nKey(data.resultCode), { ns: 'common' }));
112
+ toast.error(t(getErrori18nKey(data.resultCode)));
90
113
  setError(data.resultMessage);
91
114
  } else if (data.user && data.token) {
92
- onLogin(data.user as User, data.token);
93
-
94
115
  if (!data.user?.tnCAndPPAccepted || !data.user?.birthDate) {
95
116
  setNeedsMissingData({
96
117
  token: data.token,
97
118
  birthDate: !data.user?.birthDate,
98
119
  tnCAndPPAccepted: !data.user?.tnCAndPPAccepted,
99
120
  });
121
+ } else {
122
+ onLogin(data.user as User, data.token);
100
123
  }
101
124
  }
102
125
  })
103
126
  .catch(err => {
104
127
  console.error('[LOGIN]', err);
105
- toast.error(err);
106
128
 
107
- if (err.message) setError(err.message);
129
+ if (err.message) {
130
+ toast.error(err.message);
131
+ setError(err.message);
132
+ }
108
133
  })
109
134
  .finally(() => {
110
135
  setLoading(false);
@@ -149,7 +174,7 @@ const LoginDrawer = ({
149
174
  );
150
175
  if (resp.resultCode !== 0) {
151
176
  console.error(resp);
152
- toast.error(t(getErrori18nKey(resp.resultCode), { ns: 'common' }));
177
+ toast.error(t(getErrori18nKey(resp.resultCode)));
153
178
  setError(resp.resultMessage);
154
179
  } else {
155
180
  toast.success(t('success'));
@@ -157,23 +182,98 @@ const LoginDrawer = ({
157
182
  }
158
183
  };
159
184
 
185
+ const [password, setPassword] = useState<string>();
186
+ const [confirmPassword, setConfirmPassword] = useState<string>();
187
+ const pwdAcceptable = !password || (password && pwdRegEx.test(password));
188
+ const pwdGreen = pwdAcceptable && password && password.length >= 24;
189
+ const pwdEmpty = !password || password.length === 0;
190
+ const pwdMeterValue =
191
+ !pwdAcceptable || pwdEmpty
192
+ ? 0
193
+ : password.length < 8
194
+ ? 15
195
+ : password.length >= 32
196
+ ? 100
197
+ : (password.length - 8) * (50 / 24) + 50;
198
+
199
+ const changePassword = async (e: React.FormEvent<HTMLFormElement>) => {
200
+ e.preventDefault();
201
+
202
+ const form = e.currentTarget as HTMLFormElement;
203
+
204
+ const tenantID = form.tenant.value ?? tenant.id;
205
+ const flowID = form.flowID.value ?? userMustChangePwd?.flowID;
206
+ const eMail = form.eMail.value ?? userMustChangePwd?.eMail;
207
+ const userName = form.userName.value ?? userMustChangePwd?.userName;
208
+ const password = form.password.value ?? userMustChangePwd?.password;
209
+ const newPassword = form.newPassword.value;
210
+
211
+ if (!newPassword?.length || !pwdAcceptable) {
212
+ setError(t('login.passwordFormatError'));
213
+ return;
214
+ }
215
+
216
+ setLoading(true);
217
+
218
+ const user: User = {
219
+ tenant: tenantID,
220
+ flowID,
221
+ eMail,
222
+ userName,
223
+ password,
224
+ newPassword,
225
+ };
226
+
227
+ try {
228
+ const { user: patchedUser, token, ...resp } = await userLogin(user);
229
+
230
+ if (resp.resultCode !== 0) {
231
+ console.error(resp);
232
+ toast.error(t(getErrori18nKey(resp.resultCode)));
233
+ setError(resp.resultMessage);
234
+ } else if (patchedUser && token) {
235
+ toast.success(t('success'));
236
+ onLogin(patchedUser || user, token);
237
+ }
238
+ } catch (e) {
239
+ let err = e as Error;
240
+ console.error('[LOGIN/CHANGE PWD]', err);
241
+
242
+ if (err.message) {
243
+ toast.error(err.message);
244
+ setError(err.message);
245
+ }
246
+ } finally {
247
+ setLoading(false);
248
+ }
249
+ };
250
+
160
251
  return (
161
252
  <Drawer
162
253
  open={open}
163
254
  onClose={onClose}
164
255
  className={cx('memori--login-drawer', {
165
256
  'memori--login-drawer--logged': isUserLoggedIn,
257
+ 'memori--login-drawer--signup': showSignup,
166
258
  })}
167
259
  title={
168
- <h2 className="memori--login-drawer--title">
260
+ <span className="memori--login-drawer--title">
169
261
  {isUserLoggedIn
170
262
  ? t('login.loggedDrawerTitle', { name: user.userName })
263
+ : showSignup
264
+ ? t('login.signupDrawerTitle')
171
265
  : t('login.loginDrawerTitle')}
172
- </h2>
266
+ </span>
173
267
  }
174
268
  >
175
269
  {isUserLoggedIn ? (
176
270
  <div className="memori--login-drawer--logged">
271
+ {user.avatarURL && (
272
+ <figure className="memori--login-drawer--avatar">
273
+ <img src={user.avatarURL} alt={user.userName} />
274
+ </figure>
275
+ )}
276
+
177
277
  <Button
178
278
  primary
179
279
  onClick={() => {
@@ -182,6 +282,13 @@ const LoginDrawer = ({
182
282
  >
183
283
  {t('login.logout')}
184
284
  </Button>
285
+
286
+ <AccountForm
287
+ user={user}
288
+ loginToken={loginToken}
289
+ apiUrl={apiUrl}
290
+ onUserUpdate={user => onLogin(user, loginToken)}
291
+ />
185
292
  </div>
186
293
  ) : needsMissingData?.token?.length ? (
187
294
  <>
@@ -272,6 +379,111 @@ const LoginDrawer = ({
272
379
  </Button>
273
380
  </form>
274
381
  </>
382
+ ) : userMustChangePwd?.flowID ? (
383
+ <>
384
+ <p>{t('login.mustChangePassword')}</p>
385
+
386
+ <form
387
+ className="memori--login-drawer--form"
388
+ onSubmit={changePassword}
389
+ >
390
+ <input
391
+ type="hidden"
392
+ name="tenant"
393
+ value={userMustChangePwd.tenant ?? tenant?.id}
394
+ />
395
+ <input
396
+ type="hidden"
397
+ name="flowID"
398
+ value={userMustChangePwd.flowID}
399
+ />
400
+ <input type="hidden" name="eMail" value={userMustChangePwd.eMail} />
401
+ <input
402
+ type="hidden"
403
+ name="userName"
404
+ value={userMustChangePwd.userName}
405
+ />
406
+ <input
407
+ type="hidden"
408
+ name="password"
409
+ value={userMustChangePwd.password}
410
+ />
411
+
412
+ <label htmlFor="#newPassword">
413
+ {t('login.password')}
414
+ <input
415
+ id="newPassword"
416
+ name="newPassword"
417
+ type="password"
418
+ required
419
+ autoComplete="new-password"
420
+ placeholder={t('login.password') || 'Password'}
421
+ onChange={e => setPassword(e.target.value)}
422
+ aria-invalid={!pwdAcceptable}
423
+ />
424
+ </label>
425
+ {!pwdAcceptable && (
426
+ <p className="memori--login-drawer--inline-error">
427
+ {t('login.passwordFormatError')}
428
+ </p>
429
+ )}
430
+
431
+ <label htmlFor="#confirm-password">
432
+ {t('login.confirmPassword')}
433
+ <input
434
+ id="confirm-password"
435
+ name="confirmPassword"
436
+ type="password"
437
+ required
438
+ autoComplete="new-password"
439
+ placeholder={t('login.confirmPassword') || 'Password'}
440
+ onChange={e => setConfirmPassword(e.target.value)}
441
+ aria-invalid={
442
+ !!password?.length &&
443
+ !!confirmPassword?.length &&
444
+ password !== confirmPassword
445
+ }
446
+ />
447
+ </label>
448
+ {!!password?.length &&
449
+ !!confirmPassword?.length &&
450
+ password !== confirmPassword && (
451
+ <p className="memori--login-drawer--inline-error">
452
+ {t('login.passwordMatchingError')}
453
+ </p>
454
+ )}
455
+
456
+ <meter
457
+ className="memori--login-drawer--password-meter"
458
+ min={0}
459
+ low={33}
460
+ high={66}
461
+ optimum={80}
462
+ max={100}
463
+ value={pwdMeterValue}
464
+ id="password-strength-meter"
465
+ />
466
+ <small>
467
+ {t(
468
+ `login.pwd${
469
+ pwdGreen ? 'Strong' : pwdAcceptable ? 'Acceptable' : 'Weak'
470
+ }`
471
+ )}
472
+ </small>
473
+
474
+ <Button htmlType="submit" primary loading={loading}>
475
+ {t('confirm')}
476
+ </Button>
477
+ </form>
478
+ </>
479
+ ) : showSignup ? (
480
+ <SignupForm
481
+ tenant={tenant}
482
+ apiUrl={apiUrl}
483
+ onLogin={onLogin}
484
+ goToLogin={() => setShowSignup(false)}
485
+ __TEST__waitingForOtp={__TEST__waitingForOtp}
486
+ />
275
487
  ) : (
276
488
  <>
277
489
  <form className="memori--login-drawer--form" onSubmit={login}>
@@ -305,13 +517,9 @@ const LoginDrawer = ({
305
517
  {!tenant?.disableRegistration ? (
306
518
  <p className="memori--login-drawer--signup">
307
519
  {t('login.newUserSignUp')}{' '}
308
- <a
309
- href={`https://${
310
- tenant.name || 'www.aisuru.com'
311
- }/${lang}/auth?signup=1&redirectTo=${redirectTo}`}
312
- >
520
+ <Button outlined onClick={() => setShowSignup(true)}>
313
521
  {t('login.signUp')}
314
- </a>
522
+ </Button>
315
523
  </p>
316
524
  ) : tenant.adminEmail ? (
317
525
  <div className="memori--login-drawer--signup">
@@ -18,6 +18,30 @@ exports[`renders LoginDrawer logged with missing data unchanged 1`] = `
18
18
  </div>
19
19
  `;
20
20
 
21
+ exports[`renders LoginDrawer on change password unchanged 1`] = `
22
+ <div>
23
+ <div
24
+ style="position: fixed; top: 1px; left: 1px; width: 1px; height: 0px; padding: 0px; margin: -1px; overflow: hidden; clip: rect(0px, 0px, 0px, 0px); white-space: nowrap; border-width: 0px; display: none;"
25
+ />
26
+ </div>
27
+ `;
28
+
29
+ exports[`renders LoginDrawer on signup unchanged 1`] = `
30
+ <div>
31
+ <div
32
+ style="position: fixed; top: 1px; left: 1px; width: 1px; height: 0px; padding: 0px; margin: -1px; overflow: hidden; clip: rect(0px, 0px, 0px, 0px); white-space: nowrap; border-width: 0px; display: none;"
33
+ />
34
+ </div>
35
+ `;
36
+
37
+ exports[`renders LoginDrawer on signup waiting for otp unchanged 1`] = `
38
+ <div>
39
+ <div
40
+ style="position: fixed; top: 1px; left: 1px; width: 1px; height: 0px; padding: 0px; margin: -1px; overflow: hidden; clip: rect(0px, 0px, 0px, 0px); white-space: nowrap; border-width: 0px; display: none;"
41
+ />
42
+ </div>
43
+ `;
44
+
21
45
  exports[`renders LoginDrawer open unchanged 1`] = `
22
46
  <div>
23
47
  <div
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import SignupForm from './SignupForm';
4
+ import { tenant } from '../../mocks/data';
5
+
6
+ beforeEach(() => {
7
+ // @ts-ignore
8
+ window.IntersectionObserver = jest.fn(() => ({
9
+ observe: jest.fn(),
10
+ unobserve: jest.fn(),
11
+ disconnect: jest.fn(),
12
+ takeRecords: jest.fn(),
13
+ }));
14
+ });
15
+
16
+ it('renders SignupForm unchanged', () => {
17
+ const { container } = render(
18
+ <SignupForm
19
+ apiUrl="https://backend.memori.ai"
20
+ tenant={tenant}
21
+ onLogin={jest.fn()}
22
+ goToLogin={jest.fn()}
23
+ />
24
+ );
25
+ expect(container).toMatchSnapshot();
26
+ });
27
+
28
+ it('renders SignupForm on otp form unchanged', () => {
29
+ const { container } = render(
30
+ <SignupForm
31
+ apiUrl="https://backend.memori.ai"
32
+ tenant={tenant}
33
+ onLogin={jest.fn()}
34
+ goToLogin={jest.fn()}
35
+ __TEST__waitingForOtp
36
+ />
37
+ );
38
+ expect(container).toMatchSnapshot();
39
+ });