diff --git a/packages/apps/esm-login-app/src/change-password-button/change-password-button.component.tsx b/packages/apps/esm-login-app/src/change-password-button/change-password-button.component.tsx new file mode 100644 index 000000000..46fe48b3c --- /dev/null +++ b/packages/apps/esm-login-app/src/change-password-button/change-password-button.component.tsx @@ -0,0 +1,32 @@ +import React, { useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { Button, Switcher, SwitcherDivider } from "@carbon/react"; +import { navigate } from "@openmrs/esm-framework"; +import styles from "./change-password-button.scss"; + +export interface ChangePasswordLinkProps {} + +const ChangePasswordLink: React.FC = () => { + const { t } = useTranslation(); + const goToChangePassword = useCallback(() => { + navigate({ to: "${openmrsSpaBase}/change-password"}); + }, []); + + return ( + <> + + + + + + ); +}; + +export default ChangePasswordLink; \ No newline at end of file diff --git a/packages/apps/esm-login-app/src/change-password-button/change-password-button.scss b/packages/apps/esm-login-app/src/change-password-button/change-password-button.scss new file mode 100644 index 000000000..2cb7a24ab --- /dev/null +++ b/packages/apps/esm-login-app/src/change-password-button/change-password-button.scss @@ -0,0 +1,8 @@ +@import "../root.scss"; + +.userProfileButton { + padding-right: 0rem; + @include brand-02(background-color); + @extend .productiveHeading01; + width: 16rem; +} \ No newline at end of file diff --git a/packages/apps/esm-login-app/src/change-password-button/change-password-button.test.tsx b/packages/apps/esm-login-app/src/change-password-button/change-password-button.test.tsx new file mode 100644 index 000000000..5bf4645eb --- /dev/null +++ b/packages/apps/esm-login-app/src/change-password-button/change-password-button.test.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import ChangePasswordButton from './change-password-button.component'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { navigate } from '@openmrs/esm-framework'; + +const navigateMock = navigate as jest.Mock; + +delete window.location; +window.location = new URL('https://dev3.openmrs.org/openmrs/spa/home') as any as Location; + +describe('', () => { + beforeEach(() => { + render(); + }); + + it('should display the `Change Password` button', async () => { + const user = userEvent.setup(); + const changePasswordButton = await screen.findByRole('button', { + name: /Change Password/i, + }); + + await user.click(changePasswordButton); + + expect(navigateMock).toHaveBeenCalledWith({ + to: '${openmrsSpaBase}/change-password', + }); + }); +}); diff --git a/packages/apps/esm-login-app/src/change-password/change-password.component.tsx b/packages/apps/esm-login-app/src/change-password/change-password.component.tsx new file mode 100644 index 000000000..f0e15c1cb --- /dev/null +++ b/packages/apps/esm-login-app/src/change-password/change-password.component.tsx @@ -0,0 +1,232 @@ +import classNames from 'classnames'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import styles from './change-passwords.scss'; +import { useTranslation } from 'react-i18next'; +import { InlineNotification, PasswordInput, Tile, Button } from '@carbon/react'; +import { navigate, ExtensionSlot, setUserLanguage, useConfig, showToast } from '@openmrs/esm-framework'; +import { ButtonSet } from '@carbon/react'; +import { performPasswordChange } from './change-password.resource'; +import { performLogout } from '../redirect-logout/logout.resource'; + +export interface ChangePasswordProps {} + +const ChangePassword: React.FC = () => { + const { t } = useTranslation(); + const config = useConfig(); + const [errorMessage, setErrorMessage] = useState(''); + const [isSavingPassword, setIsSavingPassword] = useState(false); + const oldPasswordInputRef = useRef(null); + const newPasswordInputRef = useRef(null); + const confirmPasswordInputRef = useRef(null); + const formRef = useRef(null); + const [newPasswordError, setNewPasswordErr] = useState(''); + const [oldPasswordError, setOldPasswordErr] = useState(''); + const [confirmPasswordError, setConfirmPasswordError] = useState(''); + const [isOldPasswordInvalid, setIsOldPasswordInvalid] = useState(true); + const [isNewPasswordInvalid, setIsNewPasswordInvalid] = useState(true); + const [isConfirmPasswordInvalid, setIsConfirmPasswordInvalid] = useState(true); + + const [passwordInput, setPasswordInput] = useState({ + oldPassword: '', + newPassword: '', + confirmPassword: '', + }); + + const resetUserNameAndPassword = useCallback(() => { + setPasswordInput({ oldPassword: '', newPassword: '', confirmPassword: '' }); + }, []); + + useEffect(() => { + if (passwordInput.oldPassword !== '') { + handleValidation(passwordInput.oldPassword, 'oldPassword'); + } + if (passwordInput.newPassword !== '') { + handleValidation(passwordInput.newPassword, 'newPassword'); + } + if (passwordInput.confirmPassword !== '') { + handleValidation(passwordInput.confirmPassword, 'confirmPassword'); + } + }, [passwordInput]); + + const handlePasswordChange = (event) => { + const passwordInputValue = event.target.value.trim(); + const passwordInputFieldName = event.target.name; + const NewPasswordInput = { ...passwordInput, [passwordInputFieldName]: passwordInputValue }; + setPasswordInput(NewPasswordInput); + }; + + const handleValidation = (passwordInputValue, passwordInputFieldName) => { + if (passwordInputFieldName === 'newPassword') { + const uppercaseRegExp = /(?=.*?[A-Z])/; + const lowercaseRegExp = /(?=.*?[a-z])/; + const digitsRegExp = /(?=.*?[0-9])/; + const minLengthRegExp = /.{8,}/; + const passwordLength = passwordInputValue.length; + const uppercasePassword = uppercaseRegExp.test(passwordInputValue); + const lowercasePassword = lowercaseRegExp.test(passwordInputValue); + const digitsPassword = digitsRegExp.test(passwordInputValue); + const minLengthPassword = minLengthRegExp.test(passwordInputValue); + let errMsg = ''; + if (passwordLength === 0) { + errMsg = 'Password is empty'; + } else if (!uppercasePassword) { + errMsg = 'At least one Uppercase'; + } else if (!lowercasePassword) { + errMsg = 'At least one Lowercase'; + } else if (!digitsPassword) { + errMsg = 'At least one digit'; + } else if (!minLengthPassword) { + errMsg = 'At least minimum 8 characters'; + } else if (passwordInput.oldPassword.length > 0 && passwordInput.newPassword === passwordInput.oldPassword) { + errMsg = 'New password must not be the same as the old password'; + } else { + errMsg = ''; + setIsNewPasswordInvalid(false); + } + setNewPasswordErr(errMsg); + } else if ( + passwordInputFieldName === 'confirmPassword' || + (passwordInputFieldName === 'newPassword' && passwordInput.confirmPassword.length > 0) + ) { + if (passwordInput.confirmPassword !== passwordInput.newPassword) { + setConfirmPasswordError('Confirm password is must be the same as the new password'); + } else { + setConfirmPasswordError(''); + setIsConfirmPasswordInvalid(false); + } + } else { + if (passwordInput.newPassword.length > 0 && passwordInput.newPassword === passwordInput.oldPassword) { + setOldPasswordErr('Old password must not be the same as the new password'); + } else { + setOldPasswordErr(''); + setIsOldPasswordInvalid(false); + } + } + }; + + const handleSubmit = useCallback( + async (evt: React.FormEvent) => { + evt.preventDefault(); + evt.stopPropagation(); + + try { + setIsSavingPassword(true); + const response = await performPasswordChange(passwordInput.oldPassword, passwordInput.confirmPassword); + if (response.ok) { + performLogout().then(() => { + const defaultLang = document.documentElement.getAttribute('data-default-lang'); + setUserLanguage({ + locale: defaultLang, + authenticated: false, + sessionId: '', + }); + if (config.provider.type === 'oauth2') { + navigate({ to: config.provider.logoutUrl }); + } else { + navigate({ to: '${openmrsSpaBase}/login' }); + } + showToast({ + title: t('userPassword', 'User password'), + description: t('userPasswordUpdated', 'User password updated successfully'), + kind: 'success', + }); + }); + } else { + throw new Error('invalidPasswordCredentials'); + } + } catch (error) { + setIsSavingPassword(false); + setErrorMessage(error.message); + } + + return false; + }, + + [passwordInput, resetUserNameAndPassword], + ); + return ( + <> + +
+
+ {errorMessage && ( + setErrorMessage('')} + /> + )} + +
+
+ 0} + invalidText={oldPasswordError} + labelText={t('oldPassword', 'Old Password')} + name="oldPassword" + value={passwordInput.oldPassword} + onChange={handlePasswordChange} + ref={oldPasswordInputRef} + required + showPasswordLabel="Show old password" + /> + 0} + invalidText={newPasswordError} + labelText={t('newPassword', 'New Password')} + name="newPassword" + value={passwordInput.newPassword} + onChange={handlePasswordChange} + ref={newPasswordInputRef} + required + showPasswordLabel="Show new password" + /> + 0} + invalidText={confirmPasswordError} + labelText={t('confirmPassword', 'Confirm Password')} + name="confirmPassword" + value={passwordInput.confirmPassword} + onChange={handlePasswordChange} + ref={confirmPasswordInputRef} + required + showPasswordLabel="Show confirm password" + /> + + + + +
+
+
+
+
+ + ); +}; +export default ChangePassword; diff --git a/packages/apps/esm-login-app/src/change-password/change-password.resource.ts b/packages/apps/esm-login-app/src/change-password/change-password.resource.ts new file mode 100644 index 000000000..ae8feb506 --- /dev/null +++ b/packages/apps/esm-login-app/src/change-password/change-password.resource.ts @@ -0,0 +1,19 @@ +import { openmrsFetch } from '@openmrs/esm-framework'; + +export async function performPasswordChange(oldPassword: string, newPassword: string) { + const abortController = new AbortController(); + + return openmrsFetch(`/ws/rest/v1/password`, { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: { + "oldPassword": oldPassword, + "newPassword": newPassword, + }, + signal: abortController.signal, + }).then((res) => { + return res; + }); +} diff --git a/packages/apps/esm-login-app/src/change-password/change-password.test.tsx b/packages/apps/esm-login-app/src/change-password/change-password.test.tsx new file mode 100644 index 000000000..2402fcf36 --- /dev/null +++ b/packages/apps/esm-login-app/src/change-password/change-password.test.tsx @@ -0,0 +1,78 @@ +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { useConfig } from '@openmrs/esm-framework'; +import { performPasswordChange } from './change-password.resource'; +import { mockConfig } from '../../__mocks__/config.mock'; +import renderWithRouter from '../test-helpers/render-with-router'; +import ChangePassword from './change-password.component'; + +const mockedChangePassword = performPasswordChange as jest.Mock; +const mockedUseConfig = useConfig as jest.Mock; + +jest.mock('@openmrs/esm-framework', () => { + const originalModule = jest.requireActual('@openmrs/esm-framework'); + + return { + ...originalModule, + clearCurrentUser: jest.fn(), + refetchCurrentUser: jest.fn().mockReturnValue(Promise.resolve()), + getSessionStore: jest.fn().mockImplementation(() => { + return { + getState: jest.fn().mockReturnValue({ + session: { + authenticated: true, + }, + }), + }; + }), + // mock only the happy path + interpolateUrl: jest.fn().mockImplementation((url: string) => url), + }; +}); + +jest.mock('./change-password.resource', () => ({ + performPasswordChange: jest.fn(), +})); + +mockedUseConfig.mockReturnValue(mockConfig); + +describe('Change Password', () => { + it('renders the change password form', () => { + renderWithRouter( + ChangePassword, + {}, + { + route: '/change-password', + }, + ); + + screen.getByLabelText(/Old Password/i); + screen.getByLabelText(/New Password/i); + screen.getByLabelText(/Confirm Password/i); + screen.getByRole('button', { name: /Save/i }); + }); + + + it('sends the user to the login page on successful password change', async () => { + let performLogout = () => {}; + mockedChangePassword.mockImplementation(() => { + performLogout(); + return Promise.resolve({ data: { authenticated: true } }); + }); + + renderWithRouter( + ChangePassword, + {}, + { + route: '/change-password', + }, + ); + + const user = userEvent.setup(); + + await user.type(screen.getByLabelText(/Old Password/i), 'my-password'); + await user.type(screen.getByLabelText(/New Password/i), 'my-password'); + await user.type(screen.getByLabelText(/Confirm Password/i), 'my-password'); + await user.click(screen.getByRole('button', { name: /Save/i })); + }); +}); diff --git a/packages/apps/esm-login-app/src/change-password/change-passwords.scss b/packages/apps/esm-login-app/src/change-password/change-passwords.scss new file mode 100644 index 000000000..a73d00f83 --- /dev/null +++ b/packages/apps/esm-login-app/src/change-password/change-passwords.scss @@ -0,0 +1,98 @@ +@import '../root.scss'; + +.container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; +} + +.change-password-card { + border-radius: 0; + border: 1px solid $ui-03; + background-color: $ui-02; + width: 80%; + padding: 2.5rem; + position: absolute; + min-height: 21.5rem; + top: 5rem +} + +@media only screen and (min-width: 481px) { + .container { + height: 100vh; + } +} + +@media only screen and (max-width: 480px) { + .change-password-card { + margin-top: 2.5%; + border: none; + } + + .container { + height: 100vh; + } +} + +.input-group { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + + :global(.cds--text-input) { + height: 3rem; + @extend .label01; + } + + :global(.cds--text-input__field-outer-wrapper) { + width: 100%; + } + + &:not(:last-child) { + margin-bottom: 1rem; + } + :global(.cds--form-item), .buttonSet { + width: 100%; + } +} + +.buttonSet { + margin-top: 1.5rem; + width: 18rem; + + :global(.cds--btn) { + max-width: 50%; + } +} + +.back-button-div { + width: 23rem; + position: absolute; + bottom: 100%; + left: 0; +} + +.back-button { + height: 3rem; + padding-left: 0rem; + + svg { + margin-right: 0.5rem; + order: 1; + } + + span { + order: 2; + } +} + +.errorMessage { + position: absolute; + top: 0; + margin-top: 0.25rem; + margin-bottom: 3rem; + z-index: 1; +} diff --git a/packages/apps/esm-login-app/src/index.ts b/packages/apps/esm-login-app/src/index.ts index 711c58d03..9c84cb38d 100644 --- a/packages/apps/esm-login-app/src/index.ts +++ b/packages/apps/esm-login-app/src/index.ts @@ -1,9 +1,11 @@ -import { defineConfigSchema, getSyncLifecycle } from '@openmrs/esm-framework'; +import { defineConfigSchema, getSyncLifecycle, registerBreadcrumbs } from '@openmrs/esm-framework'; import { configSchema } from './config-schema'; import rootComponent from './root.component'; import locationPickerComponent from './location-picker/location-picker.component'; import changeLocationLinkComponent from './change-location-link/change-location-link.extension'; +import changePasswordButtonComponent from './change-password-button/change-password-button.component'; import logoutButtonComponent from './logout/logout.extension'; +import changePasswordComponent from './change-password/change-password.component' const moduleName = '@openmrs/esm-login-app'; @@ -16,9 +18,18 @@ export const importTranslation = require.context('../translations', false, /.jso export function startupApp() { defineConfigSchema(moduleName, configSchema); + registerBreadcrumbs([ + { + path: `${window.spaBase}/change-password`, + title: "Change user password", + parent: `${window.spaBase}/home`, + }, + ]); } export const root = getSyncLifecycle(rootComponent, options); export const locationPicker = getSyncLifecycle(locationPickerComponent, options); export const logoutButton = getSyncLifecycle(logoutButtonComponent, options); +export const changePassword = getSyncLifecycle(changePasswordComponent, options); export const changeLocationLink = getSyncLifecycle(changeLocationLinkComponent, options); +export const changePasswordButton = getSyncLifecycle(changePasswordButtonComponent, options); diff --git a/packages/apps/esm-login-app/src/root.component.tsx b/packages/apps/esm-login-app/src/root.component.tsx index 802530e75..5dcbfe94b 100644 --- a/packages/apps/esm-login-app/src/root.component.tsx +++ b/packages/apps/esm-login-app/src/root.component.tsx @@ -3,6 +3,7 @@ import Login from './login/login.component'; import LocationPicker from './location-picker/location-picker.component'; import RedirectLogout from './redirect-logout/redirect-logout.component'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import ChangePassword from './change-password/change-password.component'; export interface RootProps {} @@ -11,6 +12,7 @@ const Root: React.FC = () => { } /> + } /> } /> } /> } /> diff --git a/packages/apps/esm-login-app/src/routes.json b/packages/apps/esm-login-app/src/routes.json index adc91ef0f..2611b0a9e 100644 --- a/packages/apps/esm-login-app/src/routes.json +++ b/packages/apps/esm-login-app/src/routes.json @@ -10,6 +10,12 @@ "online": true, "offline": true }, + { + "component": "changePassword", + "route": "change-password", + "online": true, + "offline": true + }, { "component": "root", "route": "logout", @@ -39,6 +45,14 @@ "online": true, "offline": true, "order": 1 + }, + { + "name": "change-password", + "slot": "user-panel-slot", + "component": "changePasswordButton", + "online": true, + "offline": true, + "order": 1 } ] } diff --git a/packages/apps/esm-login-app/translations/am.json b/packages/apps/esm-login-app/translations/am.json index bd1d55fac..ed7b80f7b 100644 --- a/packages/apps/esm-login-app/translations/am.json +++ b/packages/apps/esm-login-app/translations/am.json @@ -1,11 +1,15 @@ { "back": "Back", "change": "Change", + "changePassword": "Change Password", "confirm": "Confirm", + "confirmPassword": "Confirm Password", "continue": "Continue", + "discard": "Discard", "error": "Error", "errorLoadingLoginLocations": "Error loading login locations", "invalidCredentials": "", + "invalidPasswordCredentials": "Invalid password provided", "loading": "Loading", "locationPreferenceAdded": "Selected location will be used for your next logins", "locationPreferenceRemoved": "Login location preference removed", @@ -13,16 +17,21 @@ "loggingIn": "Logging in", "login": "Log in", "Logout": "Logout", + "newPassword": "New Password", "noResultsToDisplay": "No results to display", + "oldPassword": "Old Password", "password": "Password", "poweredBy": "Powered by", "rememberLocationForFutureLogins": "Remember my location for future logins", "removedLoginLocationPreference": "The login location preference has been removed.", + "save": "Save", "searchForLocation": "Search for a location", "selectedLocationPreferenceSetMessage": "You can change your preference from the user menu", "selectYourLocation": "Select your location from the list below. Use the search bar to find your location.", "submitting": "Submitting", "username": "Username", + "userPassword": "User password", + "userPasswordUpdated": "User password updated successfully", "validValueRequired": "A valid value is required", "welcome": "Welcome" } diff --git a/packages/apps/esm-login-app/translations/ar.json b/packages/apps/esm-login-app/translations/ar.json index b88ef0df1..3cd8d397a 100644 --- a/packages/apps/esm-login-app/translations/ar.json +++ b/packages/apps/esm-login-app/translations/ar.json @@ -1,11 +1,15 @@ { "back": "رجوع", "change": "تغيير", + "changePassword": "Change Password", "confirm": "تأكيد", + "confirmPassword": "Confirm Password", "continue": "استمرار", + "discard": "Discard", "error": "خطأ", "errorLoadingLoginLocations": "Error loading login locations", "invalidCredentials": "بيانات الدخول غير صحيحة", + "invalidPasswordCredentials": "Invalid password provided", "loading": "جار التحميل", "locationPreferenceAdded": "Selected location will be used for your next logins", "locationPreferenceRemoved": "Login location preference removed", @@ -13,16 +17,21 @@ "loggingIn": "جار الدخول", "login": "تسجيل الدخول", "Logout": "تسجيل الخروج", + "newPassword": "New Password", "noResultsToDisplay": "لا توجد نتائج للعرض", + "oldPassword": "Old Password", "password": "كلمة المرور", "poweredBy": "مدعوم من", "rememberLocationForFutureLogins": "Remember my location for future logins", "removedLoginLocationPreference": "The login location preference has been removed.", + "save": "Save", "searchForLocation": "ابحث عن موقع", "selectedLocationPreferenceSetMessage": "You can change your preference from the user menu", "selectYourLocation": "اختر موقعك من القائمة أدناه. استخدم شريط البحث للعثور على موقعك.", "submitting": "جار الإرسال", "username": "اسم المستخدم", + "userPassword": "User password", + "userPasswordUpdated": "User password updated successfully", "validValueRequired": "قيمة صالحة مطلوبة", "welcome": "مرحبا" } diff --git a/packages/apps/esm-login-app/translations/en.json b/packages/apps/esm-login-app/translations/en.json index 0fa9d11b7..22e90faeb 100644 --- a/packages/apps/esm-login-app/translations/en.json +++ b/packages/apps/esm-login-app/translations/en.json @@ -1,11 +1,15 @@ { "back": "Back", "change": "Change", + "changePassword": "Change Password", "confirm": "Confirm", + "confirmPassword": "Confirm Password", "continue": "Continue", + "discard": "Discard", "error": "Error", "errorLoadingLoginLocations": "Error loading login locations", "invalidCredentials": "Invalid username or password", + "invalidPasswordCredentials": "Invalid password provided", "loading": "Loading", "locationPreferenceAdded": "Selected location will be used for your next logins", "locationPreferenceRemoved": "Login location preference removed", @@ -13,16 +17,21 @@ "loggingIn": "Logging in", "login": "Log in", "Logout": "Logout", + "newPassword": "New Password", "noResultsToDisplay": "No results to display", + "oldPassword": "Old Password", "password": "Password", "poweredBy": "Powered by", "rememberLocationForFutureLogins": "Remember my location for future logins", "removedLoginLocationPreference": "The login location preference has been removed.", + "save": "Save", "searchForLocation": "Search for a location", "selectedLocationPreferenceSetMessage": "You can change your preference from the user menu", "selectYourLocation": "Select your location from the list below. Use the search bar to find your location.", "submitting": "Submitting", "username": "Username", + "userPassword": "User password", + "userPasswordUpdated": "User password updated successfully", "validValueRequired": "A valid value is required", "welcome": "Welcome" } diff --git a/packages/apps/esm-login-app/translations/es.json b/packages/apps/esm-login-app/translations/es.json index 0f99631e8..4c2a43348 100644 --- a/packages/apps/esm-login-app/translations/es.json +++ b/packages/apps/esm-login-app/translations/es.json @@ -1,11 +1,15 @@ { "back": "Atrás", "change": "Cambiar", + "changePassword": "Change Password", "confirm": "Confirmar", + "confirmPassword": "Confirm Password", "continue": "Continuar", + "discard": "Discard", "error": "Error", "errorLoadingLoginLocations": "Error loading login locations", "invalidCredentials": "Nombre de usuario o contraseña inválidos", + "invalidPasswordCredentials": "Invalid password provided", "loading": "Cargando", "locationPreferenceAdded": "Selected location will be used for your next logins", "locationPreferenceRemoved": "Login location preference removed", @@ -13,16 +17,21 @@ "loggingIn": "Iniciando sesión", "login": "Iniciar sesión", "Logout": "Cerrar sesión", + "newPassword": "New Password", "noResultsToDisplay": "No hay resultados para mostrar", + "oldPassword": "Old Password", "password": "Contraseña", "poweredBy": "Desarrollado por", "rememberLocationForFutureLogins": "Remember my location for future logins", "removedLoginLocationPreference": "The login location preference has been removed.", + "save": "Save", "searchForLocation": "Buscar una ubicación", "selectedLocationPreferenceSetMessage": "You can change your preference from the user menu", "selectYourLocation": "Selecciona tu ubicación de la lista de abajo. Utiliza la barra de búsqueda para encontrar tu ubicación.", "submitting": "Enviando", "username": "Nombre de usuario", + "userPassword": "User password", + "userPasswordUpdated": "User password updated successfully", "validValueRequired": "Se requiere un valor válido", "welcome": "Bienvenido/a" } diff --git a/packages/apps/esm-login-app/translations/fr.json b/packages/apps/esm-login-app/translations/fr.json index a8b10c4b8..4c7380c23 100644 --- a/packages/apps/esm-login-app/translations/fr.json +++ b/packages/apps/esm-login-app/translations/fr.json @@ -1,11 +1,15 @@ { "back": "Retour", "change": "Changer", + "changePassword": "Change Password", "confirm": "Vérifier", + "confirmPassword": "Confirm Password", "continue": "Continuer", + "discard": "Discard", "error": "Erreur", "errorLoadingLoginLocations": "Error loading login locations", "invalidCredentials": "Nom d'utilisateur ou mot de passe invalide", + "invalidPasswordCredentials": "Invalid password provided", "loading": "En cours de chargement", "locationPreferenceAdded": "Selected location will be used for your next logins", "locationPreferenceRemoved": "Login location preference removed", @@ -13,16 +17,21 @@ "loggingIn": "Logging in", "login": "S'identifier", "Logout": "Se déconnecter", + "newPassword": "New Password", "noResultsToDisplay": "No results to display", + "oldPassword": "Old Password", "password": "Mot de passe", "poweredBy": "Propulsé par", "rememberLocationForFutureLogins": "Remember my location for future logins", "removedLoginLocationPreference": "The login location preference has been removed.", + "save": "Save", "searchForLocation": "Rechercher un emplacement", "selectedLocationPreferenceSetMessage": "You can change your preference from the user menu", "selectYourLocation": "Sélectionnez votre emplacement dans la liste ci-dessous. Utilisez la barre de recherche pour rechercher votre emplacement.", "submitting": "Submitting", "username": "Nom d'utilisateur", + "userPassword": "User password", + "userPasswordUpdated": "User password updated successfully", "validValueRequired": "Une valeur valide est requise", "welcome": "Bienvenue" } diff --git a/packages/apps/esm-login-app/translations/he.json b/packages/apps/esm-login-app/translations/he.json index 2cacf673e..d7f620796 100644 --- a/packages/apps/esm-login-app/translations/he.json +++ b/packages/apps/esm-login-app/translations/he.json @@ -1,11 +1,15 @@ { "back": "חזרה", "change": "שינוי", + "changePassword": "Change Password", "confirm": "אישור", + "confirmPassword": "Confirm Password", "continue": "המשך", + "discard": "Discard", "error": "שגיאה", "errorLoadingLoginLocations": "Error loading login locations", "invalidCredentials": "שם משתמש או סיסמה לא תקינים", + "invalidPasswordCredentials": "Invalid password provided", "loading": "טוען", "locationPreferenceAdded": "Selected location will be used for your next logins", "locationPreferenceRemoved": "Login location preference removed", @@ -13,16 +17,21 @@ "loggingIn": "Logging in", "login": "התחברות", "Logout": "התנתקות", + "newPassword": "New Password", "noResultsToDisplay": "No results to display", + "oldPassword": "Old Password", "password": "סיסמה", "poweredBy": "מופעל על ידי", "rememberLocationForFutureLogins": "Remember my location for future logins", "removedLoginLocationPreference": "The login location preference has been removed.", + "save": "Save", "searchForLocation": "חיפוש מיקום", "selectedLocationPreferenceSetMessage": "You can change your preference from the user menu", "selectYourLocation": "בחר את המיקום שלך מהרשימה למטה. השתמש בשורת החיפוש לחיפוש המיקום שלך.", "submitting": "Submitting", "username": "שם משתמש", + "userPassword": "User password", + "userPasswordUpdated": "User password updated successfully", "validValueRequired": "נדרשת ערך תקף", "welcome": "ברוכים הבאים" }