@isoftdata/svelte-user-configuration 1.2.1 → 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
|
|
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,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
|
-
|
|
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
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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>
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
confirmPasswordSet
|
|
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}
|