@memori.ai/memori-react 5.0.2 → 6.0.0-rc.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 (67) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -0
  3. package/dist/components/Header/Header.css +2 -1
  4. package/dist/components/Header/Header.d.ts +2 -0
  5. package/dist/components/Header/Header.js +3 -2
  6. package/dist/components/Header/Header.js.map +1 -1
  7. package/dist/components/LoginDrawer/LoginDrawer.css +84 -0
  8. package/dist/components/LoginDrawer/LoginDrawer.d.ts +15 -0
  9. package/dist/components/LoginDrawer/LoginDrawer.js +129 -0
  10. package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -0
  11. package/dist/components/MemoriWidget/MemoriWidget.d.ts +3 -1
  12. package/dist/components/MemoriWidget/MemoriWidget.js +36 -8
  13. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  14. package/dist/components/StartPanel/StartPanel.d.ts +2 -0
  15. package/dist/components/StartPanel/StartPanel.js +2 -2
  16. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +5 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/locales/en.json +25 -0
  21. package/dist/locales/it.json +25 -0
  22. package/dist/styles.css +1 -0
  23. package/esm/components/Header/Header.css +2 -1
  24. package/esm/components/Header/Header.d.ts +2 -0
  25. package/esm/components/Header/Header.js +3 -2
  26. package/esm/components/Header/Header.js.map +1 -1
  27. package/esm/components/LoginDrawer/LoginDrawer.css +84 -0
  28. package/esm/components/LoginDrawer/LoginDrawer.d.ts +15 -0
  29. package/esm/components/LoginDrawer/LoginDrawer.js +125 -0
  30. package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -0
  31. package/esm/components/MemoriWidget/MemoriWidget.d.ts +3 -1
  32. package/esm/components/MemoriWidget/MemoriWidget.js +36 -8
  33. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  34. package/esm/components/StartPanel/StartPanel.d.ts +2 -0
  35. package/esm/components/StartPanel/StartPanel.js +2 -2
  36. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  37. package/esm/index.d.ts +2 -0
  38. package/esm/index.js +5 -3
  39. package/esm/index.js.map +1 -1
  40. package/esm/locales/en.json +25 -0
  41. package/esm/locales/it.json +25 -0
  42. package/esm/styles.css +1 -0
  43. package/package.json +1 -1
  44. package/src/components/Header/Header.css +2 -1
  45. package/src/components/Header/Header.stories.tsx +27 -1
  46. package/src/components/Header/Header.test.tsx +14 -1
  47. package/src/components/Header/Header.tsx +17 -0
  48. package/src/components/Header/__snapshots__/Header.test.tsx.snap +260 -0
  49. package/src/components/LoginDrawer/LoginDrawer.css +84 -0
  50. package/src/components/LoginDrawer/LoginDrawer.stories.tsx +58 -0
  51. package/src/components/LoginDrawer/LoginDrawer.test.tsx +86 -0
  52. package/src/components/LoginDrawer/LoginDrawer.tsx +330 -0
  53. package/src/components/LoginDrawer/__snapshots__/LoginDrawer.test.tsx.snap +35 -0
  54. package/src/components/MemoriWidget/MemoriWidget.tsx +53 -7
  55. package/src/components/StartPanel/StartPanel.stories.tsx +3 -1
  56. package/src/components/StartPanel/StartPanel.test.tsx +12 -0
  57. package/src/components/StartPanel/StartPanel.tsx +11 -0
  58. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +7 -0
  59. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +20 -0
  60. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +20 -0
  61. package/src/components/layouts/__snapshots__/Totem.test.tsx.snap +20 -0
  62. package/src/index.stories.tsx +0 -1
  63. package/src/index.tsx +8 -0
  64. package/src/locales/en.json +25 -0
  65. package/src/locales/it.json +25 -0
  66. package/src/mocks/data.ts +2 -2
  67. package/src/styles.css +1 -0
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { Meta, Story } from '@storybook/react';
3
+ import I18nWrapper from '../../I18nWrapper';
4
+ import LoginDrawer, { Props } from './LoginDrawer';
5
+ import { tenant, user } from '../../mocks/data';
6
+ import './LoginDrawer.css';
7
+
8
+ const meta: Meta = {
9
+ title: 'Widget/LoginDrawer',
10
+ component: LoginDrawer,
11
+ argTypes: {
12
+ open: {
13
+ control: {
14
+ type: 'boolean',
15
+ },
16
+ },
17
+ },
18
+ parameters: {
19
+ controls: { expanded: true },
20
+ },
21
+ };
22
+
23
+ export default meta;
24
+
25
+ const Template: Story<Props> = args => {
26
+ return (
27
+ <I18nWrapper>
28
+ <LoginDrawer
29
+ {...args}
30
+ onClose={() => {}}
31
+ onLogin={console.log}
32
+ onLogout={() => {}}
33
+ tenant={tenant}
34
+ apiUrl="https://backend.memori.ai"
35
+ />
36
+ </I18nWrapper>
37
+ );
38
+ };
39
+
40
+ // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test
41
+ // https://storybook.js.org/docs/react/workflows/unit-testing
42
+ export const Default = Template.bind({});
43
+ Default.args = {
44
+ open: true,
45
+ };
46
+
47
+ export const NeedsMissingData = Template.bind({});
48
+ NeedsMissingData.args = {
49
+ open: true,
50
+ __TEST__needMissingData: true,
51
+ };
52
+
53
+ export const LoggedIn = Template.bind({});
54
+ LoggedIn.args = {
55
+ open: true,
56
+ user,
57
+ loginToken: 'token',
58
+ };
@@ -0,0 +1,86 @@
1
+ import { render } from '@testing-library/react';
2
+ import LoginDrawer from './LoginDrawer';
3
+ import React from 'react';
4
+ import { tenant, expertReference, user } 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 LoginDrawer closed unchanged', () => {
17
+ const { container } = render(
18
+ <LoginDrawer
19
+ onClose={jest.fn()}
20
+ apiUrl="https://backend.memori.ai"
21
+ tenant={tenant}
22
+ onLogin={jest.fn()}
23
+ onLogout={jest.fn()}
24
+ />
25
+ );
26
+ expect(container).toMatchSnapshot();
27
+ });
28
+
29
+ it('renders LoginDrawer open unchanged', () => {
30
+ const { container } = render(
31
+ <LoginDrawer
32
+ onClose={jest.fn()}
33
+ apiUrl="https://backend.memori.ai"
34
+ tenant={tenant}
35
+ onLogin={jest.fn()}
36
+ onLogout={jest.fn()}
37
+ open
38
+ />
39
+ );
40
+ expect(container).toMatchSnapshot();
41
+ });
42
+
43
+ it('renders LoginDrawer unlogged unchanged', () => {
44
+ const { container } = render(
45
+ <LoginDrawer
46
+ onClose={jest.fn()}
47
+ apiUrl="https://backend.memori.ai"
48
+ tenant={tenant}
49
+ onLogin={jest.fn()}
50
+ onLogout={jest.fn()}
51
+ open
52
+ />
53
+ );
54
+ expect(container).toMatchSnapshot();
55
+ });
56
+
57
+ it('renders LoginDrawer logged with missing data unchanged', () => {
58
+ const { container } = render(
59
+ <LoginDrawer
60
+ onClose={jest.fn()}
61
+ apiUrl="https://backend.memori.ai"
62
+ tenant={tenant}
63
+ onLogin={jest.fn()}
64
+ onLogout={jest.fn()}
65
+ open
66
+ __TEST__needMissingData
67
+ />
68
+ );
69
+ expect(container).toMatchSnapshot();
70
+ });
71
+
72
+ it('renders LoginDrawer logged in unchanged', () => {
73
+ const { container } = render(
74
+ <LoginDrawer
75
+ onClose={jest.fn()}
76
+ apiUrl="https://backend.memori.ai"
77
+ tenant={tenant}
78
+ onLogin={jest.fn()}
79
+ onLogout={jest.fn()}
80
+ open
81
+ user={user}
82
+ loginToken="token"
83
+ />
84
+ );
85
+ expect(container).toMatchSnapshot();
86
+ });
@@ -0,0 +1,330 @@
1
+ import { User, Tenant } from '@memori.ai/memori-api-client/dist/types';
2
+ import React, { useEffect, useState } from 'react';
3
+ import Button from '../ui/Button';
4
+ import Drawer from '../ui/Drawer';
5
+ import toast from 'react-hot-toast';
6
+ import { useTranslation } from 'react-i18next';
7
+ import cx from 'classnames';
8
+ import memoriApiClient from '@memori.ai/memori-api-client';
9
+ import { getErrori18nKey } from '../../helpers/error';
10
+
11
+ export const mailRegEx = /^\w+([.-]?[+]?\w+)*@\w+([.-]?\w+)*(\.\w{2,})+$/;
12
+
13
+ export interface Props {
14
+ open?: boolean;
15
+ onClose: () => void;
16
+ user?: User;
17
+ loginToken?: string;
18
+ onLogin: (user: User, token: string) => void;
19
+ onLogout: () => void;
20
+ tenant: Tenant;
21
+ apiUrl: string;
22
+ __TEST__needMissingData?: boolean;
23
+ }
24
+
25
+ const LoginDrawer = ({
26
+ open = false,
27
+ onClose,
28
+ onLogin,
29
+ onLogout,
30
+ user,
31
+ loginToken,
32
+ tenant,
33
+ apiUrl,
34
+ __TEST__needMissingData = false,
35
+ }: Props) => {
36
+ const { t, i18n } = useTranslation();
37
+ const lang = i18n.language === 'it' ? 'it' : 'en';
38
+
39
+ const client = memoriApiClient(apiUrl);
40
+ const { userLogin, updateUser } = client.backend;
41
+
42
+ const [loading, setLoading] = useState(false);
43
+ const [needsMissingData, setNeedsMissingData] = useState<{
44
+ token: string;
45
+ birthDate?: boolean;
46
+ tnCAndPPAccepted?: boolean;
47
+ }>(
48
+ __TEST__needMissingData
49
+ ? {
50
+ token: 'token',
51
+ birthDate: true,
52
+ tnCAndPPAccepted: true,
53
+ }
54
+ : ({} as any)
55
+ );
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 && loginToken;
64
+
65
+ const login = (e: React.FormEvent<HTMLFormElement>) => {
66
+ e.preventDefault();
67
+ const form = e.currentTarget as HTMLFormElement;
68
+ const userNameOrEmail = form.userNameOrEmail.value;
69
+ const password = form.password.value;
70
+
71
+ const isEmail = mailRegEx.test(userNameOrEmail);
72
+ const user: User = isEmail
73
+ ? {
74
+ tenant: tenant?.id,
75
+ eMail: userNameOrEmail,
76
+ password: password,
77
+ }
78
+ : {
79
+ tenant: tenant?.id,
80
+ userName: userNameOrEmail,
81
+ password: password,
82
+ };
83
+ setLoading(true);
84
+ setError(null);
85
+ userLogin(user)
86
+ .then(data => {
87
+ if (data.resultCode !== 0) {
88
+ console.error(data);
89
+ toast.error(t(getErrori18nKey(data.resultCode), { ns: 'common' }));
90
+ setError(data.resultMessage);
91
+ } else if (data.user && data.token) {
92
+ onLogin(data.user as User, data.token);
93
+
94
+ if (!data.user?.tnCAndPPAccepted || !data.user?.birthDate) {
95
+ setNeedsMissingData({
96
+ token: data.token,
97
+ birthDate: !data.user?.birthDate,
98
+ tnCAndPPAccepted: !data.user?.tnCAndPPAccepted,
99
+ });
100
+ }
101
+ }
102
+ })
103
+ .catch(err => {
104
+ console.error('[LOGIN]', err);
105
+ toast.error(err);
106
+
107
+ if (err.message) setError(err.message);
108
+ })
109
+ .finally(() => {
110
+ setLoading(false);
111
+ });
112
+ };
113
+
114
+ const updateMissingData = async (e: React.FormEvent<HTMLFormElement>) => {
115
+ e.preventDefault();
116
+ const form = e.currentTarget as HTMLFormElement;
117
+
118
+ const birthDate = form.birthDate?.value;
119
+ const tnCAndPPAccepted = form.tnCAndPPAccepted?.checked;
120
+ const pAndCUAccepted = form.pAndCUAccepted?.checked;
121
+
122
+ if (!user?.userID || !needsMissingData?.token) {
123
+ return;
124
+ }
125
+
126
+ if (!birthDate || !tnCAndPPAccepted) {
127
+ setError(t('missingData'));
128
+ return;
129
+ }
130
+
131
+ let newUser: Partial<User> = {
132
+ userID: user.userID,
133
+ birthDate:
134
+ user?.birthDate || !needsMissingData.birthDate ? undefined : birthDate,
135
+ tnCAndPPAccepted: tnCAndPPAccepted || user?.tnCAndPPAccepted,
136
+ tnCAndPPAcceptanceDate: tnCAndPPAccepted
137
+ ? new Date().toISOString()
138
+ : undefined,
139
+ pAndCUAccepted: pAndCUAccepted || user?.pAndCUAccepted,
140
+ pAndCUAcceptanceDate: pAndCUAccepted
141
+ ? new Date().toISOString()
142
+ : undefined,
143
+ };
144
+
145
+ const { user: patchedUser, ...resp } = await updateUser(
146
+ needsMissingData.token,
147
+ user.userID,
148
+ newUser
149
+ );
150
+ if (resp.resultCode !== 0) {
151
+ console.error(resp);
152
+ toast.error(t(getErrori18nKey(resp.resultCode), { ns: 'common' }));
153
+ setError(resp.resultMessage);
154
+ } else {
155
+ toast.success(t('success'));
156
+ onLogin(patchedUser || newUser, needsMissingData.token);
157
+ }
158
+ };
159
+
160
+ return (
161
+ <Drawer
162
+ open={open}
163
+ onClose={onClose}
164
+ className={cx('memori--login-drawer', {
165
+ 'memori--login-drawer--logged': isUserLoggedIn,
166
+ })}
167
+ title={
168
+ <h2 className="memori--login-drawer--title">
169
+ {isUserLoggedIn
170
+ ? t('login.loggedDrawerTitle', { name: user.userName })
171
+ : t('login.loginDrawerTitle')}
172
+ </h2>
173
+ }
174
+ >
175
+ {isUserLoggedIn ? (
176
+ <div className="memori--login-drawer--logged">
177
+ <Button
178
+ primary
179
+ onClick={() => {
180
+ onLogout();
181
+ }}
182
+ >
183
+ {t('login.logout')}
184
+ </Button>
185
+ </div>
186
+ ) : needsMissingData?.token?.length ? (
187
+ <>
188
+ <h3>{t('login.missingData')}</h3>
189
+ <p>{t('login.missingDataHelper')}</p>
190
+
191
+ <form
192
+ className="memori--login-drawer--form"
193
+ onSubmit={updateMissingData}
194
+ >
195
+ {needsMissingData.birthDate && (
196
+ <>
197
+ <label htmlFor="#birthDate">
198
+ {t('login.birthDate')}
199
+ <input
200
+ id="birthDate"
201
+ name="birthDate"
202
+ type="date"
203
+ required
204
+ autoComplete="bday"
205
+ />
206
+ </label>
207
+ <p>
208
+ <small>{t('login.birthDateHelper')}</small>
209
+ </p>
210
+ </>
211
+ )}
212
+
213
+ {needsMissingData?.tnCAndPPAccepted && (
214
+ <>
215
+ <label className="memori-checkbox">
216
+ <span className="memori-checkbox--input-wrapper">
217
+ <input
218
+ type="checkbox"
219
+ name="tnCAndPPAccepted"
220
+ className="memori-checkbox--input"
221
+ />
222
+ <span className="memori-checkbox--inner" />
223
+ </span>
224
+ <span className="memori-checkbox--text">
225
+ {t('login.privacyLabel')}{' '}
226
+ <a
227
+ href={`https://memori.ai/${lang}/privacy_and_cookie`}
228
+ target="_blank"
229
+ rel="noopener noreferrer"
230
+ >
231
+ {t('login.privacyAndCookiePolicy')}
232
+ </a>{' '}
233
+ {t('login.and')}{' '}
234
+ <a
235
+ href={`https://memori.ai/${lang}/tos`}
236
+ target="_blank"
237
+ rel="noopener noreferrer"
238
+ >
239
+ {t('login.termsOfService')}
240
+ </a>
241
+ </span>
242
+ </label>
243
+
244
+ <label className="memori-checkbox">
245
+ <span className="memori-checkbox--input-wrapper">
246
+ <input
247
+ type="checkbox"
248
+ name="pAndCUAccepted"
249
+ defaultChecked={user?.pAndCUAccepted}
250
+ className="memori-checkbox--input"
251
+ />
252
+ <span className="memori-checkbox--inner" />
253
+ </span>
254
+ <span className="memori-checkbox--text">
255
+ {t('login.pAndCUAccepted')}{' '}
256
+ <small>
257
+ <em>({t('login.optional')})</em>
258
+ </small>
259
+ </span>
260
+ </label>
261
+ <p>
262
+ <small>{t('login.goToAccountToChangeYourPreferences')}</small>
263
+ </p>
264
+ <p>
265
+ <small>{t('login.deepThoughtExplaination')}</small>
266
+ </p>
267
+ </>
268
+ )}
269
+
270
+ <Button htmlType="submit" primary loading={loading}>
271
+ {t('login.save')}
272
+ </Button>
273
+ </form>
274
+ </>
275
+ ) : (
276
+ <>
277
+ <form className="memori--login-drawer--form" onSubmit={login}>
278
+ <label htmlFor="#userNameOrEmail">
279
+ {t('login.userNameOrEmail')}
280
+ <input
281
+ id="userNameOrEmail"
282
+ name="userNameOrEmail"
283
+ required
284
+ autoComplete="email"
285
+ placeholder="Username/email"
286
+ />
287
+ </label>
288
+
289
+ <label htmlFor="#password">
290
+ Password
291
+ <input
292
+ id="password"
293
+ name="password"
294
+ type="password"
295
+ required
296
+ autoComplete="password"
297
+ placeholder="Password"
298
+ />
299
+ </label>
300
+
301
+ <Button htmlType="submit" primary loading={loading}>
302
+ {t('login.login')}
303
+ </Button>
304
+
305
+ {!tenant?.disableRegistration && (
306
+ <p className="memori--login-drawer--signup">
307
+ {t('login.newUserSignUp')}{' '}
308
+ <a
309
+ href={`https://${
310
+ tenant.name || 'www.aisuru.com'
311
+ }/${lang}/auth?signup=1&redirectTo=${redirectTo}`}
312
+ >
313
+ {t('login.signUp')}
314
+ </a>
315
+ </p>
316
+ )}
317
+ </form>
318
+
319
+ {error && (
320
+ <p role="alert" className="memori--login-drawer--error">
321
+ {error}
322
+ </p>
323
+ )}
324
+ </>
325
+ )}
326
+ </Drawer>
327
+ );
328
+ };
329
+
330
+ export default LoginDrawer;
@@ -0,0 +1,35 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`renders LoginDrawer closed unchanged 1`] = `<div />`;
4
+
5
+ exports[`renders LoginDrawer logged in unchanged 1`] = `
6
+ <div>
7
+ <div
8
+ 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;"
9
+ />
10
+ </div>
11
+ `;
12
+
13
+ exports[`renders LoginDrawer logged with missing data unchanged 1`] = `
14
+ <div>
15
+ <div
16
+ 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;"
17
+ />
18
+ </div>
19
+ `;
20
+
21
+ exports[`renders LoginDrawer open 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 unlogged 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
+ `;
@@ -53,6 +53,7 @@ import AgeVerificationModal from '../AgeVerificationModal/AgeVerificationModal';
53
53
  import SettingsDrawer from '../SettingsDrawer/SettingsDrawer';
54
54
  import KnownFacts from '../KnownFacts/KnownFacts';
55
55
  import ExpertsDrawer from '../ExpertsDrawer/ExpertsDrawer';
56
+ import LoginDrawer from '../LoginDrawer/LoginDrawer';
56
57
 
57
58
  // Layout
58
59
  import FullPageLayout from '../layouts/FullPage';
@@ -331,6 +332,7 @@ export interface Props {
331
332
  showClear?: boolean;
332
333
  showOnlyLastMessages?: boolean;
333
334
  showTypingText?: boolean;
335
+ showLogin?: boolean;
334
336
  preview?: boolean;
335
337
  embed?: boolean;
336
338
  height?: number | string;
@@ -349,6 +351,7 @@ export interface Props {
349
351
  };
350
352
  authToken?: string;
351
353
  AZURE_COGNITIVE_SERVICES_TTS_KEY?: string;
354
+ defaultSpeakerActive?: boolean;
352
355
  onStateChange?: (state?: DialogState) => void;
353
356
  additionalInfo?: OpenSession['additionalInfo'] & { [key: string]: string };
354
357
  customMediaRenderer?: ChatProps['customMediaRenderer'];
@@ -374,6 +377,7 @@ const MemoriWidget = ({
374
377
  showSettings = true,
375
378
  showTypingText = false,
376
379
  showClear = false,
380
+ showLogin = true,
377
381
  showOnlyLastMessages,
378
382
  height = '100vh',
379
383
  secret,
@@ -387,6 +391,7 @@ const MemoriWidget = ({
387
391
  personification,
388
392
  authToken,
389
393
  AZURE_COGNITIVE_SERVICES_TTS_KEY,
394
+ defaultSpeakerActive = true,
390
395
  onStateChange,
391
396
  additionalInfo,
392
397
  additionalSettings,
@@ -418,16 +423,22 @@ const MemoriWidget = ({
418
423
  const [loginToken, setLoginToken] = useState<string | undefined>(
419
424
  additionalInfo?.loginToken ?? authToken
420
425
  );
421
- const [user, setUser] = useState<User>({
426
+ const [user, setUser] = useState<User | undefined>({
422
427
  avatarURL: typeof userAvatar === 'string' ? userAvatar : undefined,
423
428
  } as User);
424
429
  useEffect(() => {
425
- if (loginToken) {
430
+ if (loginToken && !user?.userID) {
426
431
  client.backend.getCurrentUser(loginToken).then(({ user, resultCode }) => {
427
- if (user && resultCode === 0) setUser(user);
432
+ if (user && resultCode === 0) {
433
+ setUser(user);
434
+ setLocalConfig('loginToken', loginToken);
435
+ } else {
436
+ setLocalConfig('loginToken', undefined);
437
+ }
428
438
  });
429
439
  }
430
- }, [loginToken]);
440
+ }, [loginToken, user?.userID]);
441
+ const [showLoginDrawer, setShowLoginDrawer] = useState(false);
431
442
 
432
443
  const [clickedStart, setClickedStart] = useState(false);
433
444
  const [gotErrorInOpening, setGotErrorInOpening] = useState(false);
@@ -467,7 +478,7 @@ const MemoriWidget = ({
467
478
  const [showSettingsDrawer, setShowSettingsDrawer] = useState(false);
468
479
  const [showKnownFactsDrawer, setShowKnownFactsDrawer] = useState(false);
469
480
  const [showExpertsDrawer, setShowExpertsDrawer] = useState(false);
470
- const [muteSpeaker, setMuteSpeaker] = useState(false);
481
+ const [muteSpeaker, setMuteSpeaker] = useState(!defaultSpeakerActive);
471
482
  const [continuousSpeech, setContinuousSpeech] = useState(false);
472
483
  const [continuousSpeechTimeout, setContinuousSpeechTimeout] = useState(2);
473
484
  const [isPlayingAudio, setIsPlayingAudio] = useState(false);
@@ -504,14 +515,18 @@ const MemoriWidget = ({
504
515
  defaultControlsPosition = 'bottom';
505
516
  }
506
517
 
507
- setMuteSpeaker(getLocalConfig('muteSpeaker', false));
508
- speakerMuted = getLocalConfig('muteSpeaker', false);
518
+ setMuteSpeaker(getLocalConfig('muteSpeaker', !defaultSpeakerActive));
519
+ speakerMuted = getLocalConfig('muteSpeaker', !defaultSpeakerActive);
509
520
  setContinuousSpeech(microphoneMode === 'CONTINUOUS');
510
521
  setContinuousSpeechTimeout(getLocalConfig('continuousSpeechTimeout', 2));
511
522
  setControlsPosition(
512
523
  getLocalConfig('controlsPosition', defaultControlsPosition)
513
524
  );
514
525
  setHideEmissions(getLocalConfig('hideEmissions', false));
526
+
527
+ if (!additionalInfo?.loginToken && !authToken) {
528
+ setLoginToken(getLocalConfig<typeof loginToken>('loginToken', undefined));
529
+ }
515
530
  }, []);
516
531
 
517
532
  /**
@@ -2620,6 +2635,8 @@ const MemoriWidget = ({
2620
2635
  showReload: selectedLayout === 'TOTEM',
2621
2636
  showClear,
2622
2637
  clearHistory: () => setHistory(h => h.slice(-1)),
2638
+ showLogin,
2639
+ setShowLoginDrawer,
2623
2640
  loginToken,
2624
2641
  user,
2625
2642
  sessionID: sessionId,
@@ -2658,6 +2675,8 @@ const MemoriWidget = ({
2658
2675
  onClickStart: onClickStart,
2659
2676
  initializeTTS: initializeTTS,
2660
2677
  isUserLoggedIn: !!loginToken,
2678
+ showLogin,
2679
+ setShowLoginDrawer,
2661
2680
  user,
2662
2681
  };
2663
2682
 
@@ -2944,6 +2963,33 @@ const MemoriWidget = ({
2944
2963
  onClose={() => setShowExpertsDrawer(false)}
2945
2964
  />
2946
2965
  )}
2966
+
2967
+ {showLoginDrawer && tenant?.id && (
2968
+ <LoginDrawer
2969
+ tenant={tenant}
2970
+ apiUrl={apiUrl}
2971
+ open={!!showLoginDrawer}
2972
+ user={user}
2973
+ loginToken={loginToken}
2974
+ onClose={() => setShowLoginDrawer(false)}
2975
+ onLogin={(user, token) => {
2976
+ setUser(user);
2977
+ setLoginToken(token);
2978
+ setShowLoginDrawer(false);
2979
+ setLocalConfig('loginToken', token);
2980
+ }}
2981
+ onLogout={() => {
2982
+ if (!loginToken) return;
2983
+
2984
+ client.backend.userLogout(loginToken).then(() => {
2985
+ setShowLoginDrawer(false);
2986
+ setUser(undefined);
2987
+ setLoginToken(undefined);
2988
+ setLocalConfig('loginToken', undefined);
2989
+ });
2990
+ }}
2991
+ />
2992
+ )}
2947
2993
  </div>
2948
2994
  );
2949
2995
  };
@@ -86,7 +86,7 @@ const Template: Story<Props> = args => (
86
86
  {args.integrationConfig && (
87
87
  <style dangerouslySetInnerHTML={{ __html: integrationStylesheet }} />
88
88
  )}
89
- <StartPanel {...args} />
89
+ <StartPanel {...args} setShowLoginDrawer={() => {}} />
90
90
  </div>
91
91
  </I18nWrapper>
92
92
  );
@@ -228,6 +228,8 @@ WithDeepThoughtEnabledUnlogged.args = {
228
228
  clickedStart: false,
229
229
  onClickStart: () => {},
230
230
  isUserLoggedIn: false,
231
+ showLogin: true,
232
+ setShowLoginDrawer: () => {},
231
233
  };
232
234
 
233
235
  export const WithDeepThoughtEnabledWithoutPermissionFlag = Template.bind({});