@isoftdata/svelte-user-configuration 1.0.28 → 1.1.1

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.
package/README.md CHANGED
@@ -15,24 +15,25 @@ npm i @isoftdata/svelte-user-configuration
15
15
  | Prop | Type | Required | Default | Description |
16
16
  |------|------|----------|---------|-------------|
17
17
  | `userAccount` | `UserAccount` | ✅ | - | The user account information. |
18
- | `canToggleActive` | `CanToggleActive` | ❌ | `true` | Whether the user can be activated/deactivated. |
19
- | `canEditAccountInfo` | `CanEditAccountInfo` | ❌ | `true` | Whether account details can be edited. |
20
- | `doSendPasswordRecoveryToken` | `DoSendPasswordRecoveryToken` | ❌ | `false` | Whether to allow sending password recovery tokens. |
21
- | `hasPermissionToChangePassword` | `HasPermissionToChangePassword` | ❌ | `false` | Whether the user has permission to change passwords. |
18
+ | `canToggleActive` | boolean | ❌ | `true` | Whether the user can be activated/deactivated. |
19
+ | `canEditAccountInfo` | boolean | ❌ | `true` | Whether account details can be edited. |
20
+ | `doSendPasswordRecoveryToken` | boolean | ❌ | `false` | Whether to allow sending password recovery tokens. |
21
+ | `hasPermissionToChangePassword` | boolean | ❌ | `false` | Whether the user has permission to change passwords. |
22
+ | `myAccountMode` | boolean | ❌ | `false` | When `true`, admin controls will be hidden/disabled. This is designed to be used when using the user account info component standalone so the current session user can edit their own account info, including first & last name, password recovery email, & password. |
22
23
 
23
24
  ### Site Access Properties
24
25
 
25
26
  | Prop | Type | Required | Default | Description |
26
27
  |------|------|----------|---------|-------------|
27
28
  | `userSites` | `UserSites` | ✅ | - | List of sites the user has access to. |
28
- | `canEditSiteAccess` | `CanEditSiteAccess` | ❌ | `true` | Whether site access can be edited. |
29
+ | `canEditSiteAccess` | boolean | ❌ | `true` | Whether site access can be edited. |
29
30
 
30
31
  ### Group Membership Properties
31
32
 
32
33
  | Prop | Type | Required | Default | Description |
33
34
  |------|------|----------|---------|-------------|
34
35
  | `groupMembership` | `GroupMembership` | ✅ | - | The user's group membership information. |
35
- | `canEditGroupMembership` | `CanEditGroupMembership` | ❌ | `true` | Whether group memberships can be edited. |
36
+ | `canEditGroupMembership` | boolean | ❌ | `true` | Whether group memberships can be edited. |
36
37
 
37
38
  ### Permission List Properties
38
39
 
@@ -1,13 +1,15 @@
1
- <script>import { getContext } from "svelte";
1
+ <script>import { getContext, tick } from "svelte";
2
2
  import Icon from "@isoftdata/svelte-icon";
3
3
  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";
7
8
  import PasswordFields from "@isoftdata/svelte-password-fields";
8
9
  import DeactivateUserModal from "./DeactivateUserModal.svelte";
9
10
  import PasswordRecoveryModal from "./PasswordRecoveryModal.svelte";
10
11
  import { translate as defaultTranslate } from "@isoftdata/utility-string";
12
+ const { t: translate } = getContext("i18next") || { t: defaultTranslate };
11
13
  export let userAccount;
12
14
  export let icon = "user";
13
15
  export let canToggleActive = true;
@@ -23,12 +25,17 @@ export let doSendPasswordRecoveryToken = false;
23
25
  export let generateNewActivationPIN = () => Promise.resolve();
24
26
  export let usernameInput = void 0;
25
27
  export let cardHeight = 0;
28
+ export let myAccountMode = false;
29
+ export let passwordValidationRules = void 0;
30
+ export let showPasswordChange = false;
26
31
  let isLoading = false;
27
32
  let passwordSetModal;
28
33
  let deactivateUserModal;
29
34
  let passwordRecoveryModal;
30
35
  let activationPINInput;
31
- const { t: translate } = getContext("i18next") || { t: defaultTranslate };
36
+ let currentPasswordField;
37
+ let cardTitle = translate("configuration.user.accountInfoHeader", "Account");
38
+ let confirmPassword = "";
32
39
  async function getNewActivationPIN(sendEmail = false) {
33
40
  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?");
34
41
  if (confirm(confirmationMessage)) {
@@ -83,9 +90,9 @@ $: isCreatingNewUser = userAccount.id === null;
83
90
  {icon}
84
91
  class="mr-1"
85
92
  />
86
- {isCreatingNewUser ? translate('configuration.user.creatingNewAccountInfoHeader', 'New Account') : translate('configuration.user.accountInfoHeader', 'Account')}
93
+ {isCreatingNewUser ? translate('configuration.user.creatingNewAccountInfoHeader', 'New Account') : cardTitle}
87
94
  </h5>
88
- {#if !isCreatingNewUser && status === 'ACTIVE'}
95
+ {#if !myAccountMode && !isCreatingNewUser && status === 'ACTIVE'}
89
96
  <Button
90
97
  size="xs"
91
98
  outline
@@ -96,7 +103,7 @@ $: isCreatingNewUser = userAccount.id === null;
96
103
  >
97
104
  <span>{translate('common:deactivate', 'Deactivate')}</span>
98
105
  </Button>
99
- {:else if (!isCreatingNewUser && status === 'DEACTIVATED') || status === 'LOCKED'}
106
+ {:else if !myAccountMode && ((!isCreatingNewUser && status === 'DEACTIVATED') || status === 'LOCKED')}
100
107
  <Button
101
108
  size="xs"
102
109
  outline
@@ -160,6 +167,34 @@ $: isCreatingNewUser = userAccount.id === null;
160
167
  >
161
168
  {/if}
162
169
  </div>
170
+ {:else if myAccountMode}
171
+ <div class="col-12">
172
+ <Button
173
+ size="sm"
174
+ outline
175
+ iconClass="key"
176
+ color={showPasswordChange ? 'danger' : 'primary'}
177
+ disabled={!canEditAccountInfo}
178
+ on:click={async () => {
179
+ showPasswordChange = !showPasswordChange
180
+ await tick()
181
+ if (showPasswordChange) {
182
+ currentPasswordField?.select()
183
+ } else {
184
+ //Clear the password fields
185
+ userAccount.currentPassword = ''
186
+ userAccount.newPassword = ''
187
+ confirmPassword = ''
188
+ }
189
+ }}
190
+ >
191
+ {#if !showPasswordChange}
192
+ {translate('configuration.user.changePassword', 'Change Password')}...
193
+ {:else}
194
+ {translate('configuration.user.cancelPasswordChange', 'Cancel Password Change')}
195
+ {/if}
196
+ </Button>
197
+ </div>
163
198
  {:else}
164
199
  <div class="col-12">
165
200
  <Button
@@ -200,29 +235,33 @@ $: isCreatingNewUser = userAccount.id === null;
200
235
  },
201
236
  }}
202
237
  bind:input={usernameInput}
238
+ readonly={myAccountMode || undefined}
239
+ tabindex={myAccountMode ? -1 : undefined}
203
240
  />
204
241
  </div>
205
- <div class="col-6">
242
+ <div class="col-12 col-md-6">
206
243
  <Input
207
244
  label={translate('configuration.user.accountInfo.firstName', 'First Name')}
208
245
  bind:value={userAccount.firstName}
209
246
  maxlength={100}
210
247
  />
211
248
  </div>
212
- <div class="col-6">
249
+ <div class="col-12 col-md-6">
213
250
  <Input
214
251
  label={translate('configuration.user.accountInfo.lastName', 'Last Name')}
215
252
  bind:value={userAccount.lastName}
216
253
  maxlength={100}
217
254
  />
218
255
  </div>
219
- <div class="col-12">
256
+ <div class="col-12 col-lg-6">
220
257
  <Input
221
258
  label={translate('configuration.user.accountInfo.workEmail', 'Work Email')}
222
259
  bind:value={userAccount.workEmail}
223
260
  autocomplete="email"
224
261
  type="email"
225
262
  inputmode="email"
263
+ readonly={myAccountMode || undefined}
264
+ tabindex={myAccountMode ? -1 : undefined}
226
265
  />
227
266
  {#if workEmail && !isCreatingNewUser && status === 'PENDING_ACTIVATION'}
228
267
  <Button
@@ -235,8 +274,19 @@ $: isCreatingNewUser = userAccount.id === null;
235
274
  </Button>
236
275
  {/if}
237
276
  </div>
277
+ <div class="col-12 col-lg-6">
278
+ <Input
279
+ label={translate('configuration.user.accountInfo.passwordRecoveryModal.passwordRecoveryEmail', 'Password Recovery Email')}
280
+ bind:value={userAccount.recoveryEmail}
281
+ autocomplete="email"
282
+ type="email"
283
+ inputmode="email"
284
+ readonly={!myAccountMode || undefined}
285
+ tabindex={!myAccountMode ? -1 : undefined}
286
+ />
287
+ </div>
238
288
  <div class="col-12">
239
- {#if !isCreatingNewUser}
289
+ {#if !isCreatingNewUser && !myAccountMode}
240
290
  <TextArea
241
291
  label={translate('configuration.user.accountInfo.lockNote', 'Lock Note')}
242
292
  labelClass="py-0 mb-2"
@@ -244,16 +294,30 @@ $: isCreatingNewUser = userAccount.id === null;
244
294
  bind:value={userAccount.lockNotes}
245
295
  readonly
246
296
  />
247
- {:else if hasPermissionToChangePassword}
297
+ {:else if (hasPermissionToChangePassword && !myAccountMode) || (myAccountMode && showPasswordChange)}
298
+ {#if myAccountMode}
299
+ <Input
300
+ bind:input={currentPasswordField}
301
+ label={translate('configuration.user.accountInfo.passwordManagementModal.currentPassword', 'Current Password')}
302
+ type="password"
303
+ value={userAccount.currentPassword ?? ''}
304
+ autocomplete="current-password"
305
+ on:change={e => (userAccount.currentPassword = getEventValue(e))}
306
+ required
307
+ />
308
+ {/if}
248
309
  <!-- TODO: When we migrate to Svelte 5, switch this and the other usage of PasswordFields to use a snippet-->
249
310
  <PasswordFields
250
311
  bind:password={userAccount.newPassword}
312
+ bind:confirmPassword
251
313
  columnClass="col-12"
252
314
  passwordLabel={translate('configuration.user.accountInfo.passwordManagementModal.newPassword', 'New Password')}
253
315
  confirmPasswordLabel={translate('configuration.user.accountInfo.passwordManagementModal.confirmNewPassword', 'Confirm New Password')}
316
+ validationRules={passwordValidationRules}
254
317
  />
255
318
  {/if}
256
319
  </div>
320
+ <slot name="formFields"></slot>
257
321
  </div>
258
322
  <slot></slot>
259
323
  </div>
@@ -1,6 +1,6 @@
1
1
  import { SvelteComponent } from "svelte";
2
2
  import type { ComponentProps } from 'svelte';
3
- import type { UserAccount, ConfirmPasswordSetFn, DeactivateUserFn, HTMLDivAttributes, IconName } from './';
3
+ import type { UserAccount, ConfirmPasswordSetFn, DeactivateUserFn, HTMLDivAttributes, IconName, PasswordValidationRules } from './';
4
4
  import PasswordRecoveryModal from './PasswordRecoveryModal.svelte';
5
5
  declare const __propDef: {
6
6
  props: HTMLDivAttributes & {
@@ -21,15 +21,19 @@ declare const __propDef: {
21
21
  }) => void | Promise<void>) | undefined;
22
22
  accountInfoChanged?: (() => void | Promise<void>) | undefined;
23
23
  sendPasswordRecoveryToken?: ComponentProps<PasswordRecoveryModal>["sendPasswordRecoveryToken"];
24
- doSendPasswordRecoveryToken: boolean;
24
+ doSendPasswordRecoveryToken?: boolean;
25
25
  icon?: IconName;
26
26
  usernameInput?: HTMLInputElement | undefined;
27
27
  cardHeight?: number;
28
+ myAccountMode?: boolean;
29
+ passwordValidationRules?: PasswordValidationRules | undefined;
30
+ cardTitle?: string;
28
31
  };
29
32
  events: {
30
33
  [evt: string]: CustomEvent<any>;
31
34
  };
32
35
  slots: {
36
+ formFields: {};
33
37
  default: {};
34
38
  };
35
39
  exports?: {} | undefined;
package/dist/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export { default as PermissionList } from './PermissionList.svelte';
5
5
  export { default as UserSiteAccess } from './UserSiteAccess.svelte';
6
6
  export * from './util';
7
7
  export type * from './util';
8
+ export type { ValidationRules as PasswordValidationRules } from '@isoftdata/svelte-password-fields';
package/dist/util.d.ts CHANGED
@@ -17,6 +17,7 @@ export interface UserAccount {
17
17
  recoveryEmail: string | null;
18
18
  status: 'ACTIVE' | 'DEACTIVATED' | 'LOCKED' | 'PENDING_ACTIVATION';
19
19
  newPassword?: string;
20
+ currentPassword?: string;
20
21
  lastPasswordResetDate?: Date;
21
22
  lastAccessDate?: Date;
22
23
  userActivationData?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isoftdata/svelte-user-configuration",
3
- "version": "1.0.28",
3
+ "version": "1.1.1",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run package",
@@ -32,6 +32,7 @@
32
32
  "devDependencies": {
33
33
  "@fortawesome/fontawesome-common-types": "^6.7.2",
34
34
  "@isoftdata/prettier-config": "^1.0.3",
35
+ "@isoftdata/svelte-bootstrap-version-switcher": "^1.0.2",
35
36
  "@sveltejs/adapter-auto": "^3.0.0",
36
37
  "@sveltejs/kit": "^2.0.0",
37
38
  "@sveltejs/package": "^2.0.0",
@@ -63,7 +64,7 @@
63
64
  "@isoftdata/svelte-icon": "^1.4.0",
64
65
  "@isoftdata/svelte-input": "^1.6.0",
65
66
  "@isoftdata/svelte-modal": "^1.3.1",
66
- "@isoftdata/svelte-password-fields": "^1.3.0",
67
+ "@isoftdata/svelte-password-fields": "^1.4.1",
67
68
  "@isoftdata/svelte-select": "^1.5.0",
68
69
  "@isoftdata/svelte-table": "^1.16.0",
69
70
  "@isoftdata/svelte-textarea": "^1.2.0",