@ampath/esm-login-app 8.0.0-next.2 → 8.0.0-next.21

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 (45) hide show
  1. package/dist/4bc56e5b0b0e91da.png +0 -0
  2. package/dist/5940.js +1 -1
  3. package/dist/5976.js +1 -1
  4. package/dist/5976.js.map +1 -1
  5. package/dist/647e55b5cedf5df2.png +0 -0
  6. package/dist/7144.js +12 -12
  7. package/dist/7144.js.map +1 -1
  8. package/dist/7244.js +1 -0
  9. package/dist/7244.js.map +1 -0
  10. package/dist/a6792134b9df70c4.png +0 -0
  11. package/dist/acd6ab71c5f6bcb6.jpg +0 -0
  12. package/dist/d0bf081185f017f3.jpg +0 -0
  13. package/dist/d48e253df6a333a7.png +0 -0
  14. package/dist/esm-login-app.js +2 -2
  15. package/dist/esm-login-app.js.buildmanifest.json +48 -34
  16. package/dist/main.js +3 -3
  17. package/dist/main.js.map +1 -1
  18. package/dist/routes.json +1 -1
  19. package/package.json +1 -1
  20. package/src/assets/Taifa-Care.png +0 -0
  21. package/src/assets/ampath-logo.png +0 -0
  22. package/src/assets/dha.png +0 -0
  23. package/src/assets/gok.png +0 -0
  24. package/src/assets/medicine.jpg +0 -0
  25. package/src/assets/openmrs.jpg +0 -0
  26. package/src/common/resend-timer/resend-timer.component.tsx +7 -2
  27. package/src/config-schema.ts +32 -37
  28. package/src/declarations.d.ts +4 -0
  29. package/src/forgot-password/forgot-password.component.tsx +113 -0
  30. package/src/forgot-password/forgot-password.resource.ts +11 -0
  31. package/src/forgot-password/forgot-password.scss +51 -0
  32. package/src/forgot-password/reset-password/reset-password.component.tsx +131 -0
  33. package/src/forgot-password/reset-password/reset-password.resource.ts +11 -0
  34. package/src/login/login.component.tsx +246 -205
  35. package/src/login/login.scss +110 -4
  36. package/src/logo.component.tsx +12 -11
  37. package/src/otp/otp.component.tsx +41 -30
  38. package/src/otp/otp.module.scss +61 -0
  39. package/src/resources/otp.resource.ts +77 -26
  40. package/src/root.component.tsx +4 -0
  41. package/src/utils/get-base-url.ts +17 -2
  42. package/yarnrc.yml +2 -2
  43. package/dist/3748.js +0 -1
  44. package/dist/3748.js.map +0 -1
  45. package/src/otp/otp.scss +0 -46
@@ -1,14 +1,7 @@
1
- import React, { useState, useRef, useEffect, useCallback } from "react";
2
- import { useLocation, useNavigate } from "react-router-dom";
3
- import { useTranslation } from "react-i18next";
4
- import {
5
- Button,
6
- InlineLoading,
7
- InlineNotification,
8
- PasswordInput,
9
- TextInput,
10
- Tile,
11
- } from "@carbon/react";
1
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
2
+ import { useLocation, useNavigate } from 'react-router-dom';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Button, InlineLoading, InlineNotification, PasswordInput, TextInput, Tile } from '@carbon/react';
12
5
  import {
13
6
  ArrowRightIcon,
14
7
  getCoreTranslation,
@@ -17,45 +10,45 @@ import {
17
10
  useConfig,
18
11
  useConnectivity,
19
12
  useSession,
20
- } from "@openmrs/esm-framework";
21
- import { type ConfigSchema } from "../config-schema";
22
- import Logo from "../logo.component";
23
- import Footer from "../footer.component";
24
- import styles from "./login.scss";
25
- import { getOtp } from "../resources/otp.resource";
13
+ } from '@openmrs/esm-framework';
14
+ import { type ConfigSchema } from '../config-schema';
15
+ import Logo from '../logo.component';
16
+ import styles from './login.scss';
17
+ import { getEmailAndPhone, getOtp } from '../resources/otp.resource';
18
+ import { getOtpEnabledStatus } from '../utils/get-base-url';
19
+ import image from '../assets/medicine.jpg';
20
+ import openmrsLogo from '../assets/openmrs.jpg';
21
+ import dhaLogo from '../assets/dha.png';
22
+ import gokLogo from '../assets/gok.png';
26
23
 
27
24
  export interface LoginReferrer {
28
25
  referrer?: string;
29
26
  }
30
27
 
31
28
  const Login: React.FC = () => {
32
- const {
33
- showPasswordOnSeparateScreen,
34
- provider: loginProvider,
35
- links: loginLinks,
36
- } = useConfig<ConfigSchema>();
29
+ const { showPasswordOnSeparateScreen, provider: loginProvider, links: loginLinks } = useConfig<ConfigSchema>();
37
30
  const isLoginEnabled = useConnectivity();
38
31
  const { t } = useTranslation();
39
32
  const { user } = useSession();
40
- const location = useLocation() as unknown as Omit<Location, "state"> & {
33
+ const location = useLocation() as unknown as Omit<Location, 'state'> & {
41
34
  state: LoginReferrer;
42
35
  };
43
36
  const navigate = useNavigate();
44
37
 
45
- const [errorMessage, setErrorMessage] = useState("");
38
+ const [errorMessage, setErrorMessage] = useState('');
46
39
  const [isLoggingIn, setIsLoggingIn] = useState(false);
47
- const [password, setPassword] = useState("");
48
- const [username, setUsername] = useState("");
40
+ const [password, setPassword] = useState('');
41
+ const [username, setUsername] = useState('');
49
42
  const [showPasswordField, setShowPasswordField] = useState(false);
50
43
  const passwordInputRef = useRef<HTMLInputElement>(null);
51
44
  const usernameInputRef = useRef<HTMLInputElement>(null);
52
45
 
53
46
  useEffect(() => {
54
47
  if (!user) {
55
- if (loginProvider.type === "oauth2") {
48
+ if (loginProvider.type === 'oauth2') {
56
49
  openmrsNavigate({ to: loginProvider.loginUrl });
57
- } else if (!username && location.pathname === "/login/confirm") {
58
- navigate("/login");
50
+ } else if (!username && location.pathname === '/login/confirm') {
51
+ navigate('/login');
59
52
  }
60
53
  }
61
54
  }, [username, navigate, location, user, loginProvider]);
@@ -71,6 +64,12 @@ const Login: React.FC = () => {
71
64
  }
72
65
  }
73
66
  }, [showPasswordField, showPasswordOnSeparateScreen]);
67
+ useEffect(() => {
68
+ document.body.classList.add('hide-top-nav');
69
+ return () => {
70
+ document.body.classList.remove('hide-top-nav');
71
+ };
72
+ }, []);
74
73
 
75
74
  const continueLogin = useCallback(() => {
76
75
  const currentUsername = usernameInputRef.current?.value?.trim();
@@ -83,14 +82,8 @@ const Login: React.FC = () => {
83
82
  }
84
83
  }, []);
85
84
 
86
- const changeUsername = useCallback(
87
- (evt: React.ChangeEvent<HTMLInputElement>) => setUsername(evt.target.value),
88
- []
89
- );
90
- const changePassword = useCallback(
91
- (evt: React.ChangeEvent<HTMLInputElement>) => setPassword(evt.target.value),
92
- []
93
- );
85
+ const changeUsername = useCallback((evt: React.ChangeEvent<HTMLInputElement>) => setUsername(evt.target.value), []);
86
+ const changePassword = useCallback((evt: React.ChangeEvent<HTMLInputElement>) => setPassword(evt.target.value), []);
94
87
 
95
88
  const handleSubmit = useCallback(
96
89
  async (evt: React.FormEvent<HTMLFormElement>) => {
@@ -98,9 +91,9 @@ const Login: React.FC = () => {
98
91
  evt.stopPropagation();
99
92
 
100
93
  // If credentials were autofilled, input onChange might not have been called
101
- const currentUsername =
102
- usernameInputRef.current?.value?.trim() || username;
94
+ const currentUsername = usernameInputRef.current?.value?.trim() || username;
103
95
  const currentPassword = passwordInputRef.current?.value || password;
96
+ const isOtpEnabled: boolean = await getOtpEnabledStatus();
104
97
 
105
98
  if (showPasswordOnSeparateScreen && !showPasswordField) {
106
99
  continueLogin();
@@ -114,41 +107,86 @@ const Login: React.FC = () => {
114
107
 
115
108
  try {
116
109
  setIsLoggingIn(true);
117
- const sessionStore = await refetchCurrentUser(
118
- currentUsername,
119
- currentPassword
120
- );
110
+ const sessionStore = await refetchCurrentUser(currentUsername, currentPassword);
121
111
  const session = sessionStore.session;
122
112
  const authenticated = sessionStore?.session?.authenticated;
123
113
 
124
- if (authenticated) {
125
- if (session.sessionLocation) {
126
- let to = loginLinks?.loginSuccess || "/home";
127
- if (location?.state?.referrer) {
128
- if (location.state.referrer.startsWith("/")) {
129
- to = `\${openmrsSpaBase}${location.state.referrer}`;
130
- } else {
131
- to = location.state.referrer;
114
+ if (isOtpEnabled === true) {
115
+ if (authenticated) {
116
+ if (session.sessionLocation) {
117
+ let to = loginLinks?.loginSuccess || '/home';
118
+ if (location?.state?.referrer) {
119
+ if (location.state.referrer.startsWith('/')) {
120
+ to = `\${openmrsSpaBase}${location.state.referrer}`;
121
+ } else {
122
+ to = location.state.referrer;
123
+ }
124
+ }
125
+ const uuid = session.user.person.uuid;
126
+ try {
127
+ const { email, phone } = await getEmailAndPhone(uuid, username, password);
128
+ const res = await getOtp(username, password, email, phone);
129
+ navigate('otp', {
130
+ state: {
131
+ username,
132
+ password,
133
+ referrer: location?.state?.referrer,
134
+ message: res,
135
+ },
136
+ });
137
+ } catch (err: any) {
138
+ setErrorMessage(err.message);
139
+ return;
140
+ }
141
+ } else if (!session.sessionLocation) {
142
+ const uuid = session.user.person.uuid;
143
+ const { email, phone } = await getEmailAndPhone(uuid, username, password);
144
+ try {
145
+ const res = await getOtp(username, password, email, phone);
146
+ navigate('otp', {
147
+ state: {
148
+ username,
149
+ password,
150
+ referrer: location?.state?.referrer,
151
+ message: res,
152
+ },
153
+ });
154
+ } catch (err: any) {
155
+ setErrorMessage(err.message);
156
+ return;
132
157
  }
133
158
  }
134
-
135
- await getOtp(username, password);
136
- navigate("otp", {
137
- state: {
138
- username,
139
- password,
140
- referrer: location?.state?.referrer,
141
- },
142
- });
159
+ } else {
160
+ setErrorMessage(t('invalidCredentials', 'Invalid username or password'));
161
+ setUsername('');
162
+ setPassword('');
163
+ if (showPasswordOnSeparateScreen) {
164
+ setShowPasswordField(false);
165
+ }
143
166
  }
144
167
  } else {
145
- setErrorMessage(
146
- t("invalidCredentials", "Invalid username or password")
147
- );
148
- setUsername("");
149
- setPassword("");
150
- if (showPasswordOnSeparateScreen) {
151
- setShowPasswordField(false);
168
+ if (authenticated) {
169
+ if (session.sessionLocation) {
170
+ let to = loginLinks?.loginSuccess || '/home';
171
+ if (location?.state?.referrer) {
172
+ if (location.state.referrer.startsWith('/')) {
173
+ to = `\${openmrsSpaBase}${location.state.referrer}`;
174
+ } else {
175
+ to = location.state.referrer;
176
+ }
177
+ }
178
+
179
+ openmrsNavigate({ to });
180
+ } else {
181
+ navigate('/login/location');
182
+ }
183
+ } else {
184
+ setErrorMessage(t('invalidCredentials', 'Invalid username or password'));
185
+ setUsername('');
186
+ setPassword('');
187
+ if (showPasswordOnSeparateScreen) {
188
+ setShowPasswordField(false);
189
+ }
152
190
  }
153
191
  }
154
192
 
@@ -157,12 +195,10 @@ const Login: React.FC = () => {
157
195
  if (error instanceof Error) {
158
196
  setErrorMessage(error.message);
159
197
  } else {
160
- setErrorMessage(
161
- t("invalidCredentials", "Invalid username or password")
162
- );
198
+ setErrorMessage(t('invalidCredentials', 'Invalid username or password'));
163
199
  }
164
- setUsername("");
165
- setPassword("");
200
+ setUsername('');
201
+ setPassword('');
166
202
  if (showPasswordOnSeparateScreen) {
167
203
  setShowPasswordField(false);
168
204
  }
@@ -180,147 +216,152 @@ const Login: React.FC = () => {
180
216
  location,
181
217
  t,
182
218
  continueLogin,
183
- ]
219
+ ],
184
220
  );
185
221
 
186
- if (!loginProvider || loginProvider.type === "basic") {
222
+ const handleForgotPassword = () => {
223
+ navigate('forgot-password');
224
+ };
225
+
226
+ if (!loginProvider || loginProvider.type === 'basic') {
187
227
  return (
188
- <div className={styles.container}>
189
- <Tile className={styles.loginCard}>
190
- {errorMessage && (
191
- <div className={styles.errorMessage}>
192
- <InlineNotification
193
- kind="error"
194
- subtitle={t(errorMessage)}
195
- title={getCoreTranslation("error")}
196
- onClick={() => setErrorMessage("")}
197
- />
198
- </div>
199
- )}
200
- <div className={styles.center}>
201
- <Logo t={t} />
202
- </div>
203
- <form onSubmit={handleSubmit}>
204
- <div className={styles.inputGroup}>
205
- <TextInput
206
- id="username"
207
- type="text"
208
- name="username"
209
- autoComplete="username"
210
- labelText={t("username", "Username")}
211
- value={username}
212
- onChange={changeUsername}
213
- ref={usernameInputRef}
214
- required
215
- autoFocus
216
- />
217
- {showPasswordOnSeparateScreen ? (
218
- <>
219
- <div
220
- className={
221
- showPasswordField ? undefined : styles.hiddenPasswordField
222
- }
223
- >
224
- <PasswordInput
225
- id="password"
226
- labelText={t("password", "Password")}
227
- name="password"
228
- autoComplete="current-password"
229
- onChange={changePassword}
230
- ref={passwordInputRef}
231
- required
232
- value={password}
233
- showPasswordLabel={t("showPassword", "Show password")}
234
- invalidText={t(
235
- "validValueRequired",
236
- "A valid value is required"
237
- )}
238
- aria-hidden={!showPasswordField}
239
- tabIndex={showPasswordField ? 0 : -1}
228
+ <>
229
+ <div className={styles.wrapperContainer}>
230
+ <div className={styles.logoContainer}>
231
+ <div className={styles.container}>
232
+ <Tile className={styles.loginCard}>
233
+ {errorMessage && (
234
+ <div className={styles.errorMessage}>
235
+ <InlineNotification
236
+ kind="error"
237
+ subtitle={errorMessage}
238
+ title={getCoreTranslation('error')}
239
+ onClick={() => setErrorMessage('')}
240
240
  />
241
241
  </div>
242
- {showPasswordField ? (
243
- <Button
244
- type="submit"
245
- className={styles.continueButton}
246
- renderIcon={(props) => (
247
- <ArrowRightIcon size={24} {...props} />
248
- )}
249
- iconDescription={t(
250
- "loginButtonIconDescription",
251
- "Log in button"
252
- )}
253
- disabled={!isLoginEnabled || isLoggingIn}
254
- >
255
- {isLoggingIn ? (
256
- <InlineLoading
257
- className={styles.loader}
258
- description={t("loggingIn", "Logging in") + "..."}
259
- />
260
- ) : (
261
- t("login", "Log in")
262
- )}
263
- </Button>
264
- ) : (
265
- <Button
266
- type="submit"
267
- className={styles.continueButton}
268
- renderIcon={(props) => (
269
- <ArrowRightIcon size={24} {...props} />
270
- )}
271
- iconDescription="Continue to password"
272
- onClick={(evt) => {
273
- evt.preventDefault();
274
- continueLogin();
275
- }}
276
- disabled={!isLoginEnabled}
277
- >
278
- {t("continue", "Continue")}
279
- </Button>
280
- )}
281
- </>
282
- ) : (
283
- <>
284
- <PasswordInput
285
- id="password"
286
- labelText={t("password", "Password")}
287
- name="password"
288
- autoComplete="current-password"
289
- onChange={changePassword}
290
- ref={passwordInputRef}
291
- required
292
- value={password}
293
- showPasswordLabel={t("showPassword", "Show password")}
294
- invalidText={t(
295
- "validValueRequired",
296
- "A valid value is required"
297
- )}
298
- />
299
- <Button
300
- type="submit"
301
- className={styles.continueButton}
302
- renderIcon={(props) => (
303
- <ArrowRightIcon size={24} {...props} />
304
- )}
305
- iconDescription="Log in"
306
- disabled={!isLoginEnabled || isLoggingIn}
307
- >
308
- {isLoggingIn ? (
309
- <InlineLoading
310
- className={styles.loader}
311
- description={t("loggingIn", "Logging in") + "..."}
312
- />
242
+ )}
243
+ <div className={styles.center}>
244
+ <Logo t={t} />
245
+ </div>
246
+ <form onSubmit={handleSubmit}>
247
+ <div className={styles.inputGroup}>
248
+ <TextInput
249
+ id="username"
250
+ type="text"
251
+ name="username"
252
+ autoComplete="username"
253
+ labelText={t('username', 'Username')}
254
+ value={username}
255
+ onChange={changeUsername}
256
+ ref={usernameInputRef}
257
+ required
258
+ autoFocus
259
+ />
260
+ {showPasswordOnSeparateScreen ? (
261
+ <>
262
+ <div className={showPasswordField ? undefined : styles.hiddenPasswordField}>
263
+ <PasswordInput
264
+ id="password"
265
+ labelText={t('password', 'Password')}
266
+ name="password"
267
+ autoComplete="current-password"
268
+ onChange={changePassword}
269
+ ref={passwordInputRef}
270
+ required
271
+ value={password}
272
+ showPasswordLabel={t('showPassword', 'Show password')}
273
+ invalidText={t('validValueRequired', 'A valid value is required')}
274
+ aria-hidden={!showPasswordField}
275
+ tabIndex={showPasswordField ? 0 : -1}
276
+ />
277
+ </div>
278
+ {showPasswordField ? (
279
+ <Button
280
+ type="submit"
281
+ className={styles.continueButton}
282
+ renderIcon={(props) => <ArrowRightIcon size={24} {...props} />}
283
+ iconDescription={t('loginButtonIconDescription', 'Log in button')}
284
+ disabled={!isLoginEnabled || isLoggingIn}
285
+ >
286
+ {isLoggingIn ? (
287
+ <InlineLoading
288
+ className={styles.loader}
289
+ description={t('loggingIn', 'Logging in') + '...'}
290
+ />
291
+ ) : (
292
+ t('login', 'Log in')
293
+ )}
294
+ </Button>
295
+ ) : (
296
+ <Button
297
+ type="submit"
298
+ className={styles.continueButton}
299
+ renderIcon={(props) => <ArrowRightIcon size={24} {...props} />}
300
+ iconDescription="Continue to password"
301
+ onClick={(evt) => {
302
+ evt.preventDefault();
303
+ continueLogin();
304
+ }}
305
+ disabled={!isLoginEnabled}
306
+ >
307
+ {t('continue', 'Continue')}
308
+ </Button>
309
+ )}
310
+ </>
313
311
  ) : (
314
- t("login", "Log in")
312
+ <>
313
+ <PasswordInput
314
+ id="password"
315
+ labelText={t('password', 'Password')}
316
+ name="password"
317
+ autoComplete="current-password"
318
+ onChange={changePassword}
319
+ ref={passwordInputRef}
320
+ required
321
+ value={password}
322
+ showPasswordLabel={t('showPassword', 'Show password')}
323
+ invalidText={t('validValueRequired', 'A valid value is required')}
324
+ />
325
+ <Button
326
+ type="submit"
327
+ className={styles.continueButton}
328
+ renderIcon={(props) => <ArrowRightIcon size={24} {...props} />}
329
+ iconDescription="Log in"
330
+ disabled={!isLoginEnabled || isLoggingIn}
331
+ >
332
+ {isLoggingIn ? (
333
+ <InlineLoading
334
+ className={styles.loader}
335
+ description={t('loggingIn', 'Logging in') + '...'}
336
+ />
337
+ ) : (
338
+ t('login', 'Log in')
339
+ )}
340
+ </Button>
341
+ <div>
342
+ Forgot Password?
343
+ <a style={{ textDecoration: 'none', cursor: 'pointer' }} onClick={handleForgotPassword}>
344
+ {' '}
345
+ Click to Reset
346
+ </a>
347
+ </div>
348
+ </>
315
349
  )}
316
- </Button>
317
- </>
318
- )}
350
+ </div>
351
+ </form>
352
+ </Tile>
353
+ </div>
354
+ <div className={styles.logoSection}>
355
+ <div className={styles.leftLogos}>
356
+ <img src={gokLogo} alt="GOK logo" className={styles.supportingLogos} />
357
+ <img src={dhaLogo} alt="DHA logo" className={styles.dhaLogo} />
358
+ </div>
359
+ <img src={openmrsLogo} alt="OpenMRS logo" className={styles.openmsrsLogo} />
319
360
  </div>
320
- </form>
321
- </Tile>
322
- <Footer />
323
- </div>
361
+ </div>
362
+ <img className={styles.image} src={image} alt="TAIFA CARE" />
363
+ </div>
364
+ </>
324
365
  );
325
366
  }
326
367
  return null;