@isoftdata/svelte-user-configuration 1.2.0 → 1.2.2

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.
@@ -5,36 +5,43 @@ import Modal from "@isoftdata/svelte-modal";
5
5
  import PasswordFields from "@isoftdata/svelte-password-fields";
6
6
  import { translate as defaultTranslate } from "@isoftdata/utility-string";
7
7
  let show = false;
8
+ let currentPassword = "";
8
9
  let newPassword = "";
9
10
  let confirmPassword = "";
10
11
  let passwordMismatch = false;
11
12
  let lastResetDate = void 0;
12
- let input = void 0;
13
+ let currentPasswordInput = void 0;
14
+ let newPasswordInput = void 0;
13
15
  let passwordRecoveryEmail = void 0;
14
- let error = void 0;
15
16
  let passwordIsValid = false;
16
17
  export let validationRules = void 0;
18
+ export let changePasswordMode = false;
17
19
  export let confirmPasswordSet = void 0;
18
20
  export async function open(userAccount) {
19
- error = void 0;
21
+ currentPassword = "";
20
22
  newPassword = "";
21
23
  confirmPassword = "";
22
24
  lastResetDate = klona(userAccount?.lastPasswordResetDate);
23
25
  passwordRecoveryEmail = userAccount?.recoveryEmail;
24
26
  show = true;
25
27
  await tick();
26
- input?.focus();
28
+ if (changePasswordMode) {
29
+ currentPasswordInput?.focus();
30
+ } else {
31
+ newPasswordInput?.focus();
32
+ }
27
33
  }
28
34
  const { t: translate } = getContext("i18next") || { t: defaultTranslate };
35
+ $: passwordMismatch = !(currentPassword && newPassword && confirmPassword && newPassword === confirmPassword);
36
+ $: disablePasswordChange = changePasswordMode && !currentPassword || passwordMismatch || !newPassword || !confirmPassword || !passwordIsValid;
29
37
  </script>
30
38
 
31
39
  <Modal
32
40
  bind:show
33
- modalSize="sm"
34
- title={translate('configuration.user.accountInfo.setPasswordModal.header', 'Set Password')}
35
- confirmButtonText={translate('configuration.user.accountInfo.passwordManagementModal.setPassword', 'Set Password')}
41
+ title={changePasswordMode ? translate('configuration.user.changePassword', 'Change Password') : translate('configuration.user.setPassword', 'Set Password')}
42
+ confirmButtonText={changePasswordMode ? translate('configuration.user.changePassword', 'Change Password') : translate('configuration.user.setPassword', 'Set Password')}
36
43
  confirmButtonIcon="check"
37
- confirmButtonDisabled={passwordMismatch || !newPassword || !confirmPassword || !passwordIsValid}
44
+ confirmButtonDisabled={disablePasswordChange}
38
45
  on:close={() => (show = false)}
39
46
  confirmButtonType="submit"
40
47
  confirmButtonFormId="setPasswordForm"
@@ -43,16 +50,9 @@ const { t: translate } = getContext("i18next") || { t: defaultTranslate };
43
50
  id="setPasswordForm"
44
51
  on:submit={async e => {
45
52
  e.preventDefault()
46
- if (!passwordMismatch && confirmPasswordSet) {
47
- try {
48
- await confirmPasswordSet({ password: newPassword })
49
- show = false
50
- } catch (err) {
51
- console.error(err)
52
- if (err instanceof Error) {
53
- error = err
54
- }
55
- }
53
+ if (!disablePasswordChange && confirmPasswordSet) {
54
+ await confirmPasswordSet({ currentPassword, newPassword })
55
+ show = false
56
56
  }
57
57
  }}
58
58
  >
@@ -63,11 +63,22 @@ const { t: translate } = getContext("i18next") || { t: defaultTranslate };
63
63
  labelParentClass="form-group mb-0"
64
64
  readonly
65
65
  />
66
+ {#if changePasswordMode}
67
+ <Input
68
+ bind:input={currentPasswordInput}
69
+ label={translate('configuration.user.accountInfo.passwordManagementModal.currentPassword', 'Current Password')}
70
+ type="password"
71
+ bind:value={currentPassword}
72
+ autocomplete="current-password"
73
+ required
74
+ class={!currentPassword ? 'is-invalid' : ''}
75
+ />
76
+ <hr />
77
+ {/if}
66
78
  <PasswordFields
67
79
  bind:password={newPassword}
68
80
  bind:confirmPassword
69
- bind:passwordMismatch
70
- bind:passwordInput={input}
81
+ bind:passwordInput={newPasswordInput}
71
82
  bind:passwordIsValid
72
83
  columnClass="col-12"
73
84
  {validationRules}
@@ -3,8 +3,10 @@ import type { UserAccount, PasswordValidationRules } from './';
3
3
  declare const __propDef: {
4
4
  props: {
5
5
  validationRules?: PasswordValidationRules | undefined;
6
+ changePasswordMode?: boolean;
6
7
  confirmPasswordSet?: ((ctx: {
7
- password: string;
8
+ currentPassword: string;
9
+ newPassword: string;
8
10
  }) => void | Promise<void>) | undefined;
9
11
  open?: (userAccount?: UserAccount) => Promise<void>;
10
12
  };
@@ -4,7 +4,6 @@ import Input from "@isoftdata/svelte-input";
4
4
  import Button from "@isoftdata/svelte-button";
5
5
  import TextArea from "@isoftdata/svelte-textarea";
6
6
  import PasswordSetModal from "./PasswordSetModal.svelte";
7
- import { getEventValue } from "@isoftdata/browser-event";
8
7
  import PasswordFields from "@isoftdata/svelte-password-fields";
9
8
  import DeactivateUserModal from "./DeactivateUserModal.svelte";
10
9
  import PasswordRecoveryModal from "./PasswordRecoveryModal.svelte";
@@ -28,16 +27,11 @@ export let cardHeight = 0;
28
27
  export let myAccountMode = false;
29
28
  export let passwordValidationRules = void 0;
30
29
  export let cardTitle = translate("configuration.user.accountInfoHeader", "Account");
31
- export let showPasswordChange = false;
32
- export let passwordMismatch = false;
33
- export let passwordIsValid = false;
34
30
  let isLoading = false;
35
31
  let passwordSetModal;
36
32
  let deactivateUserModal;
37
33
  let passwordRecoveryModal;
38
34
  let activationPINInput;
39
- let currentPasswordField;
40
- let confirmPassword = "";
41
35
  async function getNewActivationPIN(sendEmail = false) {
42
36
  let confirmationMessage = sendEmail ? translate("configuration.user.permissions.sendNewActivationPINMessage", "Are you sure you want to send a new activation PIN? The user will receive an email with the new activation PIN.") : translate("configuration.user.permissions.generateNewActivationPINMessage", "Are you sure you want to generate a new activation PIN?");
43
37
  if (confirm(confirmationMessage)) {
@@ -77,7 +71,6 @@ $: workEmail = userAccount.workEmail;
77
71
  $: status = userAccount.status;
78
72
  $: activationPIN = userAccount.userActivationData?.activationPIN;
79
73
  $: isCreatingNewUser = userAccount.id === null;
80
- $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword && confirmPassword && userAccount.newPassword === confirmPassword);
81
74
  </script>
82
75
 
83
76
  <div
@@ -122,108 +115,53 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
122
115
  </div>
123
116
  <div class="card-body">
124
117
  <div class="form-row">
125
- {#if !isCreatingNewUser}
126
- {#if status === 'PENDING_ACTIVATION'}
127
- <div class="col-12">
128
- {#if activationPIN && workEmail}
129
- <div class="alert alert-info mb-0">
130
- {translate('configuration.user.accountInfo.activationPINSent', 'An activation PIN has been sent to {{email}}.', { email: workEmail })}
131
- </div>
132
- {:else}
133
- <label for="activationPIN">{translate('configuration.user.activationPIN', 'Activation PIN')}</label>
134
- <div class="input-group input-group-sm">
135
- <input
136
- bind:this={activationPINInput}
137
- type="text"
138
- class="form-control"
139
- placeholder="###-###"
140
- value={activationPIN}
141
- readonly
118
+ {#if !isCreatingNewUser && status === 'PENDING_ACTIVATION'}
119
+ <div class="col-12">
120
+ {#if activationPIN && workEmail}
121
+ <div class="alert alert-info mb-0">
122
+ {translate('configuration.user.accountInfo.activationPINSent', 'An activation PIN has been sent to {{email}}.', { email: workEmail })}
123
+ </div>
124
+ {:else}
125
+ <label for="activationPIN">{translate('configuration.user.activationPIN', 'Activation PIN')}</label>
126
+ <div class="input-group input-group-sm">
127
+ <input
128
+ bind:this={activationPINInput}
129
+ type="text"
130
+ class="form-control"
131
+ placeholder="###-###"
132
+ value={activationPIN}
133
+ readonly
134
+ />
135
+ <div class="input-group-append">
136
+ <!-- When this button is hit, the function will call an API endpoint that will write the new PIN into the db -->
137
+ <!-- Therefore, this button will ignore the save function, as we need the new PIN to display it here -->
138
+ <Button
139
+ size="sm"
140
+ outline
141
+ {isLoading}
142
+ iconClass="refresh"
143
+ on:click={() => getNewActivationPIN()}
144
+ title={translate('configuration.user.generateNewActivationPIN', 'Generate New PIN')}
145
+ />
146
+ <Button
147
+ size="sm"
148
+ outline
149
+ iconClass="copy"
150
+ on:click={() => copyTextToClipboard()}
151
+ disabled={!activationPIN}
152
+ title={translate('configuration.user.copyActivationPIN', 'Copy Activation PIN')}
142
153
  />
143
- <div class="input-group-append">
144
- <!-- When this button is hit, the function will call an API endpoint that will write the new PIN into the db -->
145
- <!-- Therefore, this button will ignore the save function, as we need the new PIN to display it here -->
146
- <Button
147
- size="sm"
148
- outline
149
- {isLoading}
150
- iconClass="refresh"
151
- on:click={() => getNewActivationPIN()}
152
- title={translate('configuration.user.generateNewActivationPIN', 'Generate New PIN')}
153
- />
154
- <Button
155
- size="sm"
156
- outline
157
- iconClass="copy"
158
- on:click={() => copyTextToClipboard()}
159
- disabled={!activationPIN}
160
- title={translate('configuration.user.copyActivationPIN', 'Copy Activation PIN')}
161
- />
162
- </div>
163
154
  </div>
164
- {/if}
165
- {#if activationPIN && userAccount.userActivationData?.activationPINExpiration}
166
- <small class="text-danger"
167
- >{translate('configuration.user.activationPINExpireText', 'Activation PIN expires on {{- date}}', {
168
- date: userAccount.userActivationData.activationPINExpiration.toLocaleString(),
169
- })}</small
170
- >
171
- {/if}
172
- </div>
173
- {:else if myAccountMode}
174
- <div class="col-12">
175
- <Button
176
- size="sm"
177
- outline
178
- iconClass="key"
179
- color={showPasswordChange ? 'danger' : 'primary'}
180
- disabled={!canEditAccountInfo}
181
- on:click={async () => {
182
- showPasswordChange = !showPasswordChange
183
- await tick()
184
- if (showPasswordChange) {
185
- currentPasswordField?.select()
186
- } else {
187
- //Clear the password fields
188
- userAccount.currentPassword = ''
189
- userAccount.newPassword = ''
190
- confirmPassword = ''
191
- }
192
- }}
193
- >
194
- {#if !showPasswordChange}
195
- {translate('configuration.user.changePassword', 'Change Password')}...
196
- {:else}
197
- {translate('configuration.user.cancelPasswordChange', 'Cancel Password Change')}
198
- {/if}
199
- </Button>
200
- </div>
201
- {:else}
202
- <div class="col-12">
203
- <Button
204
- size="sm"
205
- outline
206
- iconClass="paper-plane"
207
- disabled={!canEditAccountInfo}
208
- on:click={() => {
209
- passwordRecoveryModal.open(userAccount.workEmail, userAccount.lastPasswordResetDate)
210
- }}
155
+ </div>
156
+ {/if}
157
+ {#if activationPIN && userAccount.userActivationData?.activationPINExpiration}
158
+ <small class="text-danger"
159
+ >{translate('configuration.user.activationPINExpireText', 'Activation PIN expires on {{- date}}', {
160
+ date: userAccount.userActivationData.activationPINExpiration.toLocaleString(),
161
+ })}</small
211
162
  >
212
- {translate('configuration.user.sendResetToken', 'Send Reset Token')}...
213
- </Button>
214
- {#if hasPermissionToChangePassword}
215
- <Button
216
- size="sm"
217
- outline
218
- iconClass="key"
219
- disabled={!canEditAccountInfo}
220
- on:click={() => passwordSetModal.open(userAccount)}
221
- >
222
- {translate('configuration.user.setPassword', 'Set Password')}...
223
- </Button>
224
- {/if}
225
- </div>
226
- {/if}
163
+ {/if}
164
+ </div>
227
165
  {/if}
228
166
  <div class="col-12">
229
167
  <Input
@@ -269,11 +207,11 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
269
207
  {#if workEmail && !isCreatingNewUser && status === 'PENDING_ACTIVATION'}
270
208
  <Button
271
209
  size="sm"
272
- outline
273
- iconClass="paper-plane"
210
+ color="link"
211
+ class="p-0 mb-2"
274
212
  on:click={() => getNewActivationPIN(true)}
275
213
  >
276
- {translate('configuration.user.sendNewActivationPIN', 'Send New Activation PIN')}
214
+ {translate('configuration.user.sendNewActivationPIN', 'Send New Activation PIN')}...
277
215
  </Button>
278
216
  {/if}
279
217
  </div>
@@ -288,8 +226,8 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
288
226
  tabindex={!myAccountMode ? -1 : undefined}
289
227
  />
290
228
  </div>
291
- <div class="col-12">
292
- {#if !isCreatingNewUser && !myAccountMode}
229
+ {#if !isCreatingNewUser && !myAccountMode}
230
+ <div class="col-12">
293
231
  <TextArea
294
232
  label={translate('configuration.user.accountInfo.lockNote', 'Lock Note')}
295
233
  labelClass="py-0 mb-2"
@@ -297,34 +235,53 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
297
235
  bind:value={userAccount.lockNotes}
298
236
  readonly
299
237
  />
300
- {:else if (hasPermissionToChangePassword && !myAccountMode) || (myAccountMode && showPasswordChange)}
301
- {#if myAccountMode}
302
- <Input
303
- bind:input={currentPasswordField}
304
- label={translate('configuration.user.accountInfo.passwordManagementModal.currentPassword', 'Current Password')}
305
- type="password"
306
- value={userAccount.currentPassword ?? ''}
307
- autocomplete="current-password"
308
- on:change={e => (userAccount.currentPassword = getEventValue(e))}
309
- required
310
- />
311
- {/if}
312
- <!-- TODO: When we migrate to Svelte 5, switch this and the other usage of PasswordFields to use a snippet-->
313
- <PasswordFields
314
- bind:password={userAccount.newPassword}
315
- bind:confirmPassword
316
- bind:passwordIsValid
317
- columnClass="col-12"
318
- passwordLabel={translate('configuration.user.accountInfo.passwordManagementModal.newPassword', 'New Password')}
319
- confirmPasswordLabel={translate('configuration.user.accountInfo.passwordManagementModal.confirmNewPassword', 'Confirm New Password')}
320
- validationRules={passwordValidationRules}
321
- />
322
- {/if}
323
- </div>
238
+ </div>
239
+ {/if}
324
240
  <slot name="formFields"></slot>
325
241
  </div>
326
242
  <slot></slot>
327
243
  </div>
244
+ <div class="card-footer">
245
+ {#if !myAccountMode}
246
+ <Button
247
+ size="sm"
248
+ outline
249
+ iconClass="paper-plane"
250
+ disabled={!canEditAccountInfo}
251
+ on:click={() => {
252
+ passwordRecoveryModal.open(userAccount.workEmail, userAccount.lastPasswordResetDate)
253
+ }}
254
+ >
255
+ {translate('configuration.user.sendResetToken', 'Send Reset Token')}...
256
+ </Button>
257
+ {/if}
258
+ {#if (!myAccountMode && hasPermissionToChangePassword) || (myAccountMode && !userAccount.newPassword)}
259
+ <Button
260
+ size="sm"
261
+ outline
262
+ iconClass="key"
263
+ disabled={!myAccountMode && !canEditAccountInfo}
264
+ on:click={() => passwordSetModal.open(userAccount)}
265
+ >
266
+ {#if myAccountMode}
267
+ {translate('configuration.user.changePassword', 'Change Password')}...
268
+ {:else}
269
+ {translate('configuration.user.setPassword', 'Set Password')}...
270
+ {/if}
271
+ </Button>
272
+ {:else if myAccountMode && userAccount.newPassword}
273
+ <Button
274
+ outline
275
+ color="danger"
276
+ on:click={() => {
277
+ userAccount.currentPassword = ''
278
+ userAccount.newPassword = ''
279
+ }}
280
+ >
281
+ {translate('configuration.user.cancelPasswordChange', 'Cancel Password Change')}</Button
282
+ >
283
+ {/if}
284
+ </div>
328
285
  </fieldset>
329
286
  </div>
330
287
 
@@ -353,10 +310,27 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
353
310
 
354
311
  <PasswordSetModal
355
312
  bind:this={passwordSetModal}
356
- confirmPasswordSet={async ({ password }) => {
357
- if (hasPermissionToChangePassword) {
358
- userAccount.newPassword = password
359
- confirmPasswordSet?.({ password })
313
+ changePasswordMode={myAccountMode}
314
+ confirmPasswordSet={async ({ currentPassword, newPassword }) => {
315
+ if (hasPermissionToChangePassword || myAccountMode) {
316
+ if (confirmPasswordSet) {
317
+ try {
318
+ await confirmPasswordSet({ currentPassword, newPassword })
319
+ await success?.({
320
+ heading: translate('configuration.user.passwordChangeSuccess', 'Password Changed!'),
321
+ message: translate('configuration.user.passwordChangeSuccess', 'Password changed successfully'),
322
+ })
323
+ } catch (err) {
324
+ await error?.({
325
+ heading: translate('configuration.user.passwordChangeFailure', 'Failed To Change Password'),
326
+ message: err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'),
327
+ })
328
+ }
329
+ } else {
330
+ //Guess if they didn't give us a confirmPasswordSet function to call we'll just update the userAccount object and they can handle it later
331
+ userAccount.currentPassword = currentPassword
332
+ userAccount.newPassword = newPassword
333
+ }
360
334
  }
361
335
  }}
362
336
  validationRules={passwordValidationRules}
@@ -28,8 +28,7 @@ declare const __propDef: {
28
28
  myAccountMode?: boolean;
29
29
  passwordValidationRules?: PasswordValidationRules | undefined;
30
30
  cardTitle?: string;
31
- showPasswordChange?: boolean;
32
- passwordMismatch?: boolean;
31
+ passwordIsValid?: boolean;
33
32
  };
34
33
  events: {
35
34
  [evt: string]: CustomEvent<any>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isoftdata/svelte-user-configuration",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run package",