@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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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={
|
|
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 (!
|
|
47
|
-
|
|
48
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
confirmPasswordSet
|
|
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}
|