@isoftdata/svelte-user-configuration 1.2.1 → 1.2.3

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,110 +115,55 @@ $: 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
- <div class="col-12">
166
+ <div class="col-12 col-lg-6">
229
167
  <Input
230
168
  label={translate('configuration.user.accountInfo.username', 'Username')}
231
169
  bind:value={userAccount.name}
@@ -242,6 +180,7 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
242
180
  tabindex={myAccountMode ? -1 : undefined}
243
181
  />
244
182
  </div>
183
+ <div class="col-12 col-lg-6"></div>
245
184
  <div class="col-12 col-md-6">
246
185
  <Input
247
186
  label={translate('configuration.user.accountInfo.firstName', 'First Name')}
@@ -269,11 +208,11 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
269
208
  {#if workEmail && !isCreatingNewUser && status === 'PENDING_ACTIVATION'}
270
209
  <Button
271
210
  size="sm"
272
- outline
273
- iconClass="paper-plane"
211
+ color="link"
212
+ class="p-0 mb-2"
274
213
  on:click={() => getNewActivationPIN(true)}
275
214
  >
276
- {translate('configuration.user.sendNewActivationPIN', 'Send New Activation PIN')}
215
+ {translate('configuration.user.sendNewActivationPIN', 'Send New Activation PIN')}...
277
216
  </Button>
278
217
  {/if}
279
218
  </div>
@@ -288,8 +227,8 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
288
227
  tabindex={!myAccountMode ? -1 : undefined}
289
228
  />
290
229
  </div>
291
- <div class="col-12">
292
- {#if !isCreatingNewUser && !myAccountMode}
230
+ {#if !isCreatingNewUser && !myAccountMode}
231
+ <div class="col-12">
293
232
  <TextArea
294
233
  label={translate('configuration.user.accountInfo.lockNote', 'Lock Note')}
295
234
  labelClass="py-0 mb-2"
@@ -297,34 +236,53 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
297
236
  bind:value={userAccount.lockNotes}
298
237
  readonly
299
238
  />
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>
239
+ </div>
240
+ {/if}
324
241
  <slot name="formFields"></slot>
325
242
  </div>
326
243
  <slot></slot>
327
244
  </div>
245
+ <div class="card-footer">
246
+ {#if !myAccountMode}
247
+ <Button
248
+ size="sm"
249
+ outline
250
+ iconClass="paper-plane"
251
+ disabled={!canEditAccountInfo}
252
+ on:click={() => {
253
+ passwordRecoveryModal.open(userAccount.workEmail, userAccount.lastPasswordResetDate)
254
+ }}
255
+ >
256
+ {translate('configuration.user.sendResetToken', 'Send Reset Token')}...
257
+ </Button>
258
+ {/if}
259
+ {#if (!myAccountMode && hasPermissionToChangePassword) || (myAccountMode && !userAccount.newPassword)}
260
+ <Button
261
+ size="sm"
262
+ outline
263
+ iconClass="key"
264
+ disabled={!myAccountMode && !canEditAccountInfo}
265
+ on:click={() => passwordSetModal.open(userAccount)}
266
+ >
267
+ {#if myAccountMode}
268
+ {translate('configuration.user.changePassword', 'Change Password')}...
269
+ {:else}
270
+ {translate('configuration.user.setPassword', 'Set Password')}...
271
+ {/if}
272
+ </Button>
273
+ {:else if myAccountMode && userAccount.newPassword}
274
+ <Button
275
+ outline
276
+ color="danger"
277
+ on:click={() => {
278
+ userAccount.currentPassword = ''
279
+ userAccount.newPassword = ''
280
+ }}
281
+ >
282
+ {translate('configuration.user.cancelPasswordChange', 'Cancel Password Change')}</Button
283
+ >
284
+ {/if}
285
+ </div>
328
286
  </fieldset>
329
287
  </div>
330
288
 
@@ -353,10 +311,27 @@ $: passwordMismatch = !(userAccount.currentPassword && userAccount.newPassword &
353
311
 
354
312
  <PasswordSetModal
355
313
  bind:this={passwordSetModal}
356
- confirmPasswordSet={async ({ password }) => {
357
- if (hasPermissionToChangePassword) {
358
- userAccount.newPassword = password
359
- confirmPasswordSet?.({ password })
314
+ changePasswordMode={myAccountMode}
315
+ confirmPasswordSet={async ({ currentPassword, newPassword }) => {
316
+ if (hasPermissionToChangePassword || myAccountMode) {
317
+ if (confirmPasswordSet) {
318
+ try {
319
+ await confirmPasswordSet({ currentPassword, newPassword })
320
+ await success?.({
321
+ heading: translate('configuration.user.passwordChangeSuccess', 'Password Changed!'),
322
+ message: translate('configuration.user.passwordChangeSuccess', 'Password changed successfully'),
323
+ })
324
+ } catch (err) {
325
+ await error?.({
326
+ heading: translate('configuration.user.passwordChangeFailure', 'Failed To Change Password'),
327
+ message: err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'),
328
+ })
329
+ }
330
+ } else {
331
+ //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
332
+ userAccount.currentPassword = currentPassword
333
+ userAccount.newPassword = newPassword
334
+ }
360
335
  }
361
336
  }}
362
337
  validationRules={passwordValidationRules}
@@ -28,8 +28,6 @@ declare const __propDef: {
28
28
  myAccountMode?: boolean;
29
29
  passwordValidationRules?: PasswordValidationRules | undefined;
30
30
  cardTitle?: string;
31
- showPasswordChange?: boolean;
32
- passwordMismatch?: boolean;
33
31
  passwordIsValid?: boolean;
34
32
  };
35
33
  events: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isoftdata/svelte-user-configuration",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run package",