@isoftdata/svelte-user-configuration 2.0.3 → 2.0.4
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 +161 -161
- package/dist/DeactivateUserModal.svelte +59 -59
- package/dist/PasswordRecoveryModal.svelte +83 -83
- package/dist/PasswordSetModal.svelte +127 -127
- package/dist/PermissionList.svelte +282 -282
- package/dist/UserAccountInfo.svelte +439 -439
- package/dist/UserGroupMembership.svelte +64 -64
- package/dist/UserSiteAccess.svelte +65 -65
- package/package.json +81 -80
|
@@ -1,439 +1,439 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { i18n } from 'i18next'
|
|
3
|
-
import type { HTMLDivAttributes } from './'
|
|
4
|
-
import type { ComponentProps, Snippet } from 'svelte'
|
|
5
|
-
import type { UserAccount, ConfirmPasswordSetFn, DeactivateUserFn, IconName, PasswordValidationRules } from './'
|
|
6
|
-
|
|
7
|
-
import { getContext } from 'svelte'
|
|
8
|
-
import Icon from '@isoftdata/svelte-icon'
|
|
9
|
-
import Input from '@isoftdata/svelte-input'
|
|
10
|
-
import Button from '@isoftdata/svelte-button'
|
|
11
|
-
import TextArea from '@isoftdata/svelte-textarea'
|
|
12
|
-
import PasswordSetModal from './PasswordSetModal.svelte'
|
|
13
|
-
import DeactivateUserModal from './DeactivateUserModal.svelte'
|
|
14
|
-
import PasswordRecoveryModal from './PasswordRecoveryModal.svelte'
|
|
15
|
-
import { translate as defaultTranslate } from '@isoftdata/utility-string'
|
|
16
|
-
|
|
17
|
-
const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate }
|
|
18
|
-
|
|
19
|
-
interface Props extends HTMLDivAttributes {
|
|
20
|
-
userAccount: UserAccount
|
|
21
|
-
canEditAccountInfo?: boolean
|
|
22
|
-
canToggleActive?: boolean
|
|
23
|
-
hasPermissionToChangePassword?: boolean
|
|
24
|
-
generateNewActivationPIN?: (userName: string, hasWorkEmail: boolean) => Promise<void>
|
|
25
|
-
confirmPasswordSet?: ConfirmPasswordSetFn
|
|
26
|
-
deactivateUser?: DeactivateUserFn
|
|
27
|
-
success?: ((info: { heading: string; message: string }) => void | Promise<void>) | undefined
|
|
28
|
-
error?: ((info: { heading: string; message: string }) => void | Promise<void>) | undefined
|
|
29
|
-
accountInfoChanged?: (() => void | Promise<void>) | undefined
|
|
30
|
-
sendPasswordRecoveryToken?: ComponentProps<typeof PasswordRecoveryModal>['sendPasswordRecoveryToken']
|
|
31
|
-
doSendPasswordRecoveryToken?: boolean
|
|
32
|
-
icon?: IconName
|
|
33
|
-
usernameInput?: HTMLInputElement | undefined
|
|
34
|
-
cardHeight?: number
|
|
35
|
-
myAccountMode?: boolean
|
|
36
|
-
passwordValidationRules?: PasswordValidationRules | undefined
|
|
37
|
-
cardTitle?: string
|
|
38
|
-
recoveryEmailIsValid?: boolean
|
|
39
|
-
workEmailIsValid?: boolean
|
|
40
|
-
formFields?: Snippet
|
|
41
|
-
children?: Snippet
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let {
|
|
45
|
-
userAccount = $bindable(),
|
|
46
|
-
canEditAccountInfo = true,
|
|
47
|
-
canToggleActive = true,
|
|
48
|
-
hasPermissionToChangePassword = false,
|
|
49
|
-
generateNewActivationPIN = () => Promise.resolve(),
|
|
50
|
-
confirmPasswordSet = undefined,
|
|
51
|
-
deactivateUser = undefined,
|
|
52
|
-
success = undefined,
|
|
53
|
-
error = undefined,
|
|
54
|
-
accountInfoChanged = undefined,
|
|
55
|
-
sendPasswordRecoveryToken = undefined,
|
|
56
|
-
doSendPasswordRecoveryToken = $bindable(false),
|
|
57
|
-
icon = 'user',
|
|
58
|
-
usernameInput = $bindable(undefined),
|
|
59
|
-
cardHeight = $bindable(),
|
|
60
|
-
myAccountMode = false,
|
|
61
|
-
passwordValidationRules = undefined,
|
|
62
|
-
cardTitle = translate('configuration.user.accountInfoHeader', 'Account'),
|
|
63
|
-
recoveryEmailIsValid = $bindable(true),
|
|
64
|
-
workEmailIsValid = $bindable(true),
|
|
65
|
-
formFields,
|
|
66
|
-
children,
|
|
67
|
-
...rest
|
|
68
|
-
}: Props = $props()
|
|
69
|
-
|
|
70
|
-
let isLoading = $state(false)
|
|
71
|
-
let passwordSetModal: PasswordSetModal | undefined = $state()
|
|
72
|
-
let passwordRecoveryModal: PasswordRecoveryModal | undefined = $state()
|
|
73
|
-
let deactivateUserModal: DeactivateUserModal | undefined = $state()
|
|
74
|
-
let activationPINInput: HTMLInputElement | undefined = $state()
|
|
75
|
-
|
|
76
|
-
async function getNewActivationPIN(sendEmail: boolean = false) {
|
|
77
|
-
let confirmationMessage: string = sendEmail
|
|
78
|
-
? translate(
|
|
79
|
-
'configuration.user.permissions.sendNewActivationPINMessage',
|
|
80
|
-
'Are you sure you want to send a new activation PIN? The user will receive an email with the new activation PIN.',
|
|
81
|
-
)
|
|
82
|
-
: translate(
|
|
83
|
-
'configuration.user.permissions.generateNewActivationPINMessage',
|
|
84
|
-
'Are you sure you want to generate a new activation PIN?',
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
if (confirm(confirmationMessage)) {
|
|
88
|
-
try {
|
|
89
|
-
isLoading = true
|
|
90
|
-
await generateNewActivationPIN(userAccount.name, !!userAccount.workEmail)
|
|
91
|
-
|
|
92
|
-
let successMessage: string = sendEmail
|
|
93
|
-
? translate(
|
|
94
|
-
'configuration.user.permissions.activationPINSent',
|
|
95
|
-
'Email with new activation PIN has been sent successfully.',
|
|
96
|
-
)
|
|
97
|
-
: translate('configuration.user.permissions.activationPINGenerated', 'Activation PIN generated successfully.')
|
|
98
|
-
let successHeading: string = sendEmail
|
|
99
|
-
? translate('configuration.messageHeading.activationPINSent', 'New Activation PIN Sent!')
|
|
100
|
-
: translate('configuration.messageHeading.activationPINGenerated', 'New Activation PIN Generated!')
|
|
101
|
-
|
|
102
|
-
await success?.({ heading: successHeading, message: successMessage })
|
|
103
|
-
|
|
104
|
-
isLoading = false
|
|
105
|
-
await accountInfoChanged?.()
|
|
106
|
-
} catch (err) {
|
|
107
|
-
console.error(err)
|
|
108
|
-
await error?.({
|
|
109
|
-
heading: translate('configuration.messageHeading.failedToGenerateNewPIN', 'Failed To Generate New PIN'),
|
|
110
|
-
message:
|
|
111
|
-
err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'),
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function copyTextToClipboard() {
|
|
118
|
-
if (activationPINInput) {
|
|
119
|
-
navigator.clipboard.writeText(activationPINInput.value)
|
|
120
|
-
await success?.({
|
|
121
|
-
heading: translate('configuration.messageHeading.activationPINCopied', 'Activation PIN Copied!'),
|
|
122
|
-
message: translate(
|
|
123
|
-
'configuration.user.permissions.activationPINCopied',
|
|
124
|
-
'Activation PIN copied to clipboard successfully.',
|
|
125
|
-
),
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function reactivateUserAccount() {
|
|
131
|
-
if (
|
|
132
|
-
confirm(
|
|
133
|
-
translate(
|
|
134
|
-
'configuration.user.permissions.reactivateUserConfirmation',
|
|
135
|
-
'Are you sure you want to reactivate this user?',
|
|
136
|
-
),
|
|
137
|
-
)
|
|
138
|
-
) {
|
|
139
|
-
userAccount.status = 'ACTIVE'
|
|
140
|
-
userAccount.lockNotes = null
|
|
141
|
-
await accountInfoChanged?.()
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const emailAddressRegex =
|
|
146
|
-
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
|
147
|
-
const emailIsValid = (emailAddress: string): boolean => emailAddressRegex.test(emailAddress)
|
|
148
|
-
|
|
149
|
-
let workEmail = $derived(userAccount.workEmail)
|
|
150
|
-
let status = $derived(userAccount.status)
|
|
151
|
-
let activationPIN = $derived(userAccount.userActivationData?.activationPIN)
|
|
152
|
-
let isCreatingNewUser = $derived(userAccount.id === null)
|
|
153
|
-
</script>
|
|
154
|
-
|
|
155
|
-
<div
|
|
156
|
-
class="card"
|
|
157
|
-
bind:offsetHeight={cardHeight}
|
|
158
|
-
{...rest}
|
|
159
|
-
>
|
|
160
|
-
<fieldset disabled={!canEditAccountInfo}>
|
|
161
|
-
<div class="card-header">
|
|
162
|
-
<div class="d-flex justify-content-between align-items-center">
|
|
163
|
-
<h5 class="mb-0">
|
|
164
|
-
<Icon
|
|
165
|
-
{icon}
|
|
166
|
-
class="mr-1 me-1"
|
|
167
|
-
/>
|
|
168
|
-
{isCreatingNewUser ? translate('configuration.user.creatingNewAccountInfoHeader', 'New Account') : cardTitle}
|
|
169
|
-
</h5>
|
|
170
|
-
{#if !myAccountMode && !isCreatingNewUser && (status === 'ACTIVE' || status === 'PENDING_ACTIVATION')}
|
|
171
|
-
<Button
|
|
172
|
-
size="xs"
|
|
173
|
-
outline
|
|
174
|
-
color="danger"
|
|
175
|
-
iconClass="xmark"
|
|
176
|
-
onclick={() => deactivateUserModal?.open(userAccount)}
|
|
177
|
-
disabled={!canEditAccountInfo || !canToggleActive}
|
|
178
|
-
>
|
|
179
|
-
<span>{translate('common:deactivate', 'Deactivate')}</span>
|
|
180
|
-
</Button>
|
|
181
|
-
{:else if !myAccountMode && ((!isCreatingNewUser && status === 'DEACTIVATED') || status === 'LOCKED')}
|
|
182
|
-
<Button
|
|
183
|
-
size="xs"
|
|
184
|
-
outline
|
|
185
|
-
color="success"
|
|
186
|
-
iconClass="check"
|
|
187
|
-
onclick={() => reactivateUserAccount()}
|
|
188
|
-
disabled={!canEditAccountInfo || !canToggleActive}
|
|
189
|
-
>
|
|
190
|
-
<span>{translate('common:activate', 'Activate')}</span>
|
|
191
|
-
</Button>
|
|
192
|
-
{/if}
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
<div class="card-body">
|
|
196
|
-
<div class="row">
|
|
197
|
-
{#if !isCreatingNewUser && status === 'PENDING_ACTIVATION'}
|
|
198
|
-
<div class="col-12">
|
|
199
|
-
{#if activationPIN && workEmail}
|
|
200
|
-
<div class="alert alert-info mb-0">
|
|
201
|
-
{translate(
|
|
202
|
-
'configuration.user.accountInfo.activationPINSent',
|
|
203
|
-
'An activation PIN has been sent to {{email}}.',
|
|
204
|
-
{ email: workEmail },
|
|
205
|
-
)}
|
|
206
|
-
</div>
|
|
207
|
-
{:else}
|
|
208
|
-
<label for="activationPIN">{translate('configuration.user.activationPIN', 'Activation PIN')}</label>
|
|
209
|
-
<div class="input-group input-group-sm">
|
|
210
|
-
<input
|
|
211
|
-
bind:this={activationPINInput}
|
|
212
|
-
type="text"
|
|
213
|
-
class="form-control"
|
|
214
|
-
placeholder="###-###"
|
|
215
|
-
value={activationPIN}
|
|
216
|
-
readonly
|
|
217
|
-
/>
|
|
218
|
-
<div class="input-group-append">
|
|
219
|
-
<!-- When this button is hit, the function will call an API endpoint that will write the new PIN into the db -->
|
|
220
|
-
<!-- Therefore, this button will ignore the save function, as we need the new PIN to display it here -->
|
|
221
|
-
<Button
|
|
222
|
-
size="sm"
|
|
223
|
-
outline
|
|
224
|
-
{isLoading}
|
|
225
|
-
iconClass="refresh"
|
|
226
|
-
onclick={() => getNewActivationPIN()}
|
|
227
|
-
title={translate('configuration.user.generateNewActivationPIN', 'Generate New PIN')}
|
|
228
|
-
/>
|
|
229
|
-
<Button
|
|
230
|
-
size="sm"
|
|
231
|
-
outline
|
|
232
|
-
iconClass="copy"
|
|
233
|
-
onclick={() => copyTextToClipboard()}
|
|
234
|
-
disabled={!activationPIN}
|
|
235
|
-
title={translate('configuration.user.copyActivationPIN', 'Copy Activation PIN')}
|
|
236
|
-
/>
|
|
237
|
-
</div>
|
|
238
|
-
</div>
|
|
239
|
-
{/if}
|
|
240
|
-
{#if activationPIN && userAccount.userActivationData?.activationPINExpiration}
|
|
241
|
-
<small class="text-danger"
|
|
242
|
-
>{translate('configuration.user.activationPINExpireText', 'Activation PIN expires on {{- date}}', {
|
|
243
|
-
date: userAccount.userActivationData.activationPINExpiration.toLocaleString(),
|
|
244
|
-
})}</small
|
|
245
|
-
>
|
|
246
|
-
{/if}
|
|
247
|
-
</div>
|
|
248
|
-
{/if}
|
|
249
|
-
<div class="col-12 col-lg-6">
|
|
250
|
-
<Input
|
|
251
|
-
label={translate('configuration.user.accountInfo.username', 'Username')}
|
|
252
|
-
bind:value={userAccount.name}
|
|
253
|
-
maxlength={320}
|
|
254
|
-
required={isCreatingNewUser}
|
|
255
|
-
validation={{
|
|
256
|
-
validator: value => {
|
|
257
|
-
if (!value) return 'Username is required.'
|
|
258
|
-
else return value.length > 320 ? 'Username must be less than 320 characters.' : true
|
|
259
|
-
},
|
|
260
|
-
}}
|
|
261
|
-
bind:input={usernameInput}
|
|
262
|
-
readonly={myAccountMode || undefined}
|
|
263
|
-
tabindex={myAccountMode ? -1 : undefined}
|
|
264
|
-
/>
|
|
265
|
-
</div>
|
|
266
|
-
<div class="col-12 col-lg-6"></div>
|
|
267
|
-
<div class="col-12 col-md-6">
|
|
268
|
-
<Input
|
|
269
|
-
label={translate('configuration.user.accountInfo.firstName', 'First Name')}
|
|
270
|
-
bind:value={userAccount.firstName}
|
|
271
|
-
maxlength={100}
|
|
272
|
-
/>
|
|
273
|
-
</div>
|
|
274
|
-
<div class="col-12 col-md-6">
|
|
275
|
-
<Input
|
|
276
|
-
label={translate('configuration.user.accountInfo.lastName', 'Last Name')}
|
|
277
|
-
bind:value={userAccount.lastName}
|
|
278
|
-
maxlength={100}
|
|
279
|
-
/>
|
|
280
|
-
</div>
|
|
281
|
-
<div class="col-12 col-lg-6">
|
|
282
|
-
<Input
|
|
283
|
-
label={translate('configuration.user.accountInfo.workEmail', 'Work Email')}
|
|
284
|
-
bind:value={userAccount.workEmail}
|
|
285
|
-
onchange={() => {
|
|
286
|
-
workEmailIsValid = !myAccountMode && userAccount.workEmail ? emailIsValid(userAccount.workEmail) : true
|
|
287
|
-
}}
|
|
288
|
-
autocomplete="email"
|
|
289
|
-
type="email"
|
|
290
|
-
inputmode="email"
|
|
291
|
-
readonly={myAccountMode || undefined}
|
|
292
|
-
tabindex={myAccountMode ? -1 : undefined}
|
|
293
|
-
hint={!workEmailIsValid ? translate('common:invalidEmailAddress', 'Invalid Email') : undefined}
|
|
294
|
-
hintClass="text-danger"
|
|
295
|
-
/>
|
|
296
|
-
{#if workEmail && !isCreatingNewUser && status === 'PENDING_ACTIVATION'}
|
|
297
|
-
<Button
|
|
298
|
-
size="sm"
|
|
299
|
-
color="link"
|
|
300
|
-
class="p-0 mb-2"
|
|
301
|
-
onclick={() => getNewActivationPIN(true)}
|
|
302
|
-
>
|
|
303
|
-
{translate('configuration.user.sendNewActivationPIN', 'Send New Activation PIN')}...
|
|
304
|
-
</Button>
|
|
305
|
-
{/if}
|
|
306
|
-
</div>
|
|
307
|
-
<div class="col-12 col-lg-6">
|
|
308
|
-
<Input
|
|
309
|
-
label={translate(
|
|
310
|
-
'configuration.user.accountInfo.passwordRecoveryModal.passwordRecoveryEmail',
|
|
311
|
-
'Password Recovery Email',
|
|
312
|
-
)}
|
|
313
|
-
bind:value={userAccount.recoveryEmail}
|
|
314
|
-
onchange={() => {
|
|
315
|
-
recoveryEmailIsValid =
|
|
316
|
-
myAccountMode && userAccount.recoveryEmail ? emailIsValid(userAccount.recoveryEmail) : true
|
|
317
|
-
}}
|
|
318
|
-
autocomplete="email"
|
|
319
|
-
type="email"
|
|
320
|
-
inputmode="email"
|
|
321
|
-
readonly={!myAccountMode || undefined}
|
|
322
|
-
tabindex={!myAccountMode ? -1 : undefined}
|
|
323
|
-
hint={!recoveryEmailIsValid ? translate('common:invalidEmailAddress', 'Invalid Email') : undefined}
|
|
324
|
-
hintClass="text-danger"
|
|
325
|
-
/>
|
|
326
|
-
</div>
|
|
327
|
-
{#if !isCreatingNewUser && !myAccountMode}
|
|
328
|
-
<div class="col-12">
|
|
329
|
-
<TextArea
|
|
330
|
-
label={translate('configuration.user.accountInfo.lockNote', 'Lock Note')}
|
|
331
|
-
labelClass="py-0 mb-2"
|
|
332
|
-
style="min-height:83px;"
|
|
333
|
-
bind:value={userAccount.lockNotes}
|
|
334
|
-
readonly
|
|
335
|
-
/>
|
|
336
|
-
</div>
|
|
337
|
-
{/if}
|
|
338
|
-
{@render formFields?.()}
|
|
339
|
-
</div>
|
|
340
|
-
{@render children?.()}
|
|
341
|
-
</div>
|
|
342
|
-
<div class="card-footer">
|
|
343
|
-
{#if !myAccountMode}
|
|
344
|
-
<Button
|
|
345
|
-
size="sm"
|
|
346
|
-
outline
|
|
347
|
-
iconClass="paper-plane"
|
|
348
|
-
disabled={!canEditAccountInfo}
|
|
349
|
-
onclick={() => {
|
|
350
|
-
passwordRecoveryModal?.open(userAccount.
|
|
351
|
-
}}
|
|
352
|
-
>
|
|
353
|
-
{translate('configuration.user.sendResetToken', 'Send Reset Token')}...
|
|
354
|
-
</Button>
|
|
355
|
-
{/if}
|
|
356
|
-
{#if (!myAccountMode && hasPermissionToChangePassword) || (myAccountMode && !userAccount.newPassword)}
|
|
357
|
-
<Button
|
|
358
|
-
size="sm"
|
|
359
|
-
outline
|
|
360
|
-
iconClass="key"
|
|
361
|
-
disabled={!myAccountMode && !canEditAccountInfo}
|
|
362
|
-
onclick={() => passwordSetModal?.open(userAccount)}
|
|
363
|
-
>
|
|
364
|
-
{#if myAccountMode}
|
|
365
|
-
{translate('configuration.user.changePassword', 'Change Password')}...
|
|
366
|
-
{:else}
|
|
367
|
-
{translate('configuration.user.setPassword', 'Set Password')}...
|
|
368
|
-
{/if}
|
|
369
|
-
</Button>
|
|
370
|
-
{:else if myAccountMode && userAccount.newPassword}
|
|
371
|
-
<Button
|
|
372
|
-
outline
|
|
373
|
-
color="danger"
|
|
374
|
-
onclick={() => {
|
|
375
|
-
userAccount.currentPassword = ''
|
|
376
|
-
userAccount.newPassword = ''
|
|
377
|
-
}}
|
|
378
|
-
>
|
|
379
|
-
{translate('configuration.user.cancelPasswordChange', 'Cancel Password Change')}</Button
|
|
380
|
-
>
|
|
381
|
-
{/if}
|
|
382
|
-
</div>
|
|
383
|
-
</fieldset>
|
|
384
|
-
</div>
|
|
385
|
-
|
|
386
|
-
<PasswordRecoveryModal
|
|
387
|
-
bind:this={passwordRecoveryModal}
|
|
388
|
-
bind:doSendPasswordRecoveryToken
|
|
389
|
-
{sendPasswordRecoveryToken}
|
|
390
|
-
/>
|
|
391
|
-
|
|
392
|
-
<DeactivateUserModal
|
|
393
|
-
bind:this={deactivateUserModal}
|
|
394
|
-
deactivateUser={async ({ lockNotes }) => {
|
|
395
|
-
userAccount.status = 'DEACTIVATED'
|
|
396
|
-
userAccount.lockNotes = lockNotes
|
|
397
|
-
try {
|
|
398
|
-
await deactivateUser?.({ id: userAccount.id, lockNotes })
|
|
399
|
-
await accountInfoChanged?.()
|
|
400
|
-
} catch (err) {
|
|
401
|
-
console.error(err)
|
|
402
|
-
await error?.({
|
|
403
|
-
heading: translate('configuration.user.messageHeading.failedToDeactivateUser', 'Failed To Deactivate User'),
|
|
404
|
-
message: err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'),
|
|
405
|
-
})
|
|
406
|
-
}
|
|
407
|
-
}}
|
|
408
|
-
/>
|
|
409
|
-
|
|
410
|
-
<PasswordSetModal
|
|
411
|
-
bind:this={passwordSetModal}
|
|
412
|
-
changePasswordMode={myAccountMode}
|
|
413
|
-
confirmPasswordSet={async ({ currentPassword, newPassword }) => {
|
|
414
|
-
if (hasPermissionToChangePassword || myAccountMode) {
|
|
415
|
-
if (confirmPasswordSet) {
|
|
416
|
-
try {
|
|
417
|
-
await confirmPasswordSet({ currentPassword, newPassword })
|
|
418
|
-
await success?.({
|
|
419
|
-
heading: translate('configuration.user.passwordChangeSuccessHeading', 'Password Changed!'),
|
|
420
|
-
message: translate('configuration.user.passwordChangeSuccessMessage', 'Password changed successfully'),
|
|
421
|
-
})
|
|
422
|
-
} catch (err) {
|
|
423
|
-
console.error(err)
|
|
424
|
-
await error?.({
|
|
425
|
-
heading: translate('configuration.user.passwordChangeErrorHeading', 'Failed To Change Password'),
|
|
426
|
-
message:
|
|
427
|
-
err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'),
|
|
428
|
-
})
|
|
429
|
-
throw err
|
|
430
|
-
}
|
|
431
|
-
} else {
|
|
432
|
-
//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
|
|
433
|
-
userAccount.currentPassword = currentPassword
|
|
434
|
-
userAccount.newPassword = newPassword
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}}
|
|
438
|
-
validationRules={passwordValidationRules}
|
|
439
|
-
/>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { i18n } from 'i18next'
|
|
3
|
+
import type { HTMLDivAttributes } from './'
|
|
4
|
+
import type { ComponentProps, Snippet } from 'svelte'
|
|
5
|
+
import type { UserAccount, ConfirmPasswordSetFn, DeactivateUserFn, IconName, PasswordValidationRules } from './'
|
|
6
|
+
|
|
7
|
+
import { getContext } from 'svelte'
|
|
8
|
+
import Icon from '@isoftdata/svelte-icon'
|
|
9
|
+
import Input from '@isoftdata/svelte-input'
|
|
10
|
+
import Button from '@isoftdata/svelte-button'
|
|
11
|
+
import TextArea from '@isoftdata/svelte-textarea'
|
|
12
|
+
import PasswordSetModal from './PasswordSetModal.svelte'
|
|
13
|
+
import DeactivateUserModal from './DeactivateUserModal.svelte'
|
|
14
|
+
import PasswordRecoveryModal from './PasswordRecoveryModal.svelte'
|
|
15
|
+
import { translate as defaultTranslate } from '@isoftdata/utility-string'
|
|
16
|
+
|
|
17
|
+
const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate }
|
|
18
|
+
|
|
19
|
+
interface Props extends HTMLDivAttributes {
|
|
20
|
+
userAccount: UserAccount
|
|
21
|
+
canEditAccountInfo?: boolean
|
|
22
|
+
canToggleActive?: boolean
|
|
23
|
+
hasPermissionToChangePassword?: boolean
|
|
24
|
+
generateNewActivationPIN?: (userName: string, hasWorkEmail: boolean) => Promise<void>
|
|
25
|
+
confirmPasswordSet?: ConfirmPasswordSetFn
|
|
26
|
+
deactivateUser?: DeactivateUserFn
|
|
27
|
+
success?: ((info: { heading: string; message: string }) => void | Promise<void>) | undefined
|
|
28
|
+
error?: ((info: { heading: string; message: string }) => void | Promise<void>) | undefined
|
|
29
|
+
accountInfoChanged?: (() => void | Promise<void>) | undefined
|
|
30
|
+
sendPasswordRecoveryToken?: ComponentProps<typeof PasswordRecoveryModal>['sendPasswordRecoveryToken']
|
|
31
|
+
doSendPasswordRecoveryToken?: boolean
|
|
32
|
+
icon?: IconName
|
|
33
|
+
usernameInput?: HTMLInputElement | undefined
|
|
34
|
+
cardHeight?: number
|
|
35
|
+
myAccountMode?: boolean
|
|
36
|
+
passwordValidationRules?: PasswordValidationRules | undefined
|
|
37
|
+
cardTitle?: string
|
|
38
|
+
recoveryEmailIsValid?: boolean
|
|
39
|
+
workEmailIsValid?: boolean
|
|
40
|
+
formFields?: Snippet
|
|
41
|
+
children?: Snippet
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let {
|
|
45
|
+
userAccount = $bindable(),
|
|
46
|
+
canEditAccountInfo = true,
|
|
47
|
+
canToggleActive = true,
|
|
48
|
+
hasPermissionToChangePassword = false,
|
|
49
|
+
generateNewActivationPIN = () => Promise.resolve(),
|
|
50
|
+
confirmPasswordSet = undefined,
|
|
51
|
+
deactivateUser = undefined,
|
|
52
|
+
success = undefined,
|
|
53
|
+
error = undefined,
|
|
54
|
+
accountInfoChanged = undefined,
|
|
55
|
+
sendPasswordRecoveryToken = undefined,
|
|
56
|
+
doSendPasswordRecoveryToken = $bindable(false),
|
|
57
|
+
icon = 'user',
|
|
58
|
+
usernameInput = $bindable(undefined),
|
|
59
|
+
cardHeight = $bindable(),
|
|
60
|
+
myAccountMode = false,
|
|
61
|
+
passwordValidationRules = undefined,
|
|
62
|
+
cardTitle = translate('configuration.user.accountInfoHeader', 'Account'),
|
|
63
|
+
recoveryEmailIsValid = $bindable(true),
|
|
64
|
+
workEmailIsValid = $bindable(true),
|
|
65
|
+
formFields,
|
|
66
|
+
children,
|
|
67
|
+
...rest
|
|
68
|
+
}: Props = $props()
|
|
69
|
+
|
|
70
|
+
let isLoading = $state(false)
|
|
71
|
+
let passwordSetModal: PasswordSetModal | undefined = $state()
|
|
72
|
+
let passwordRecoveryModal: PasswordRecoveryModal | undefined = $state()
|
|
73
|
+
let deactivateUserModal: DeactivateUserModal | undefined = $state()
|
|
74
|
+
let activationPINInput: HTMLInputElement | undefined = $state()
|
|
75
|
+
|
|
76
|
+
async function getNewActivationPIN(sendEmail: boolean = false) {
|
|
77
|
+
let confirmationMessage: string = sendEmail
|
|
78
|
+
? translate(
|
|
79
|
+
'configuration.user.permissions.sendNewActivationPINMessage',
|
|
80
|
+
'Are you sure you want to send a new activation PIN? The user will receive an email with the new activation PIN.',
|
|
81
|
+
)
|
|
82
|
+
: translate(
|
|
83
|
+
'configuration.user.permissions.generateNewActivationPINMessage',
|
|
84
|
+
'Are you sure you want to generate a new activation PIN?',
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if (confirm(confirmationMessage)) {
|
|
88
|
+
try {
|
|
89
|
+
isLoading = true
|
|
90
|
+
await generateNewActivationPIN(userAccount.name, !!userAccount.workEmail)
|
|
91
|
+
|
|
92
|
+
let successMessage: string = sendEmail
|
|
93
|
+
? translate(
|
|
94
|
+
'configuration.user.permissions.activationPINSent',
|
|
95
|
+
'Email with new activation PIN has been sent successfully.',
|
|
96
|
+
)
|
|
97
|
+
: translate('configuration.user.permissions.activationPINGenerated', 'Activation PIN generated successfully.')
|
|
98
|
+
let successHeading: string = sendEmail
|
|
99
|
+
? translate('configuration.messageHeading.activationPINSent', 'New Activation PIN Sent!')
|
|
100
|
+
: translate('configuration.messageHeading.activationPINGenerated', 'New Activation PIN Generated!')
|
|
101
|
+
|
|
102
|
+
await success?.({ heading: successHeading, message: successMessage })
|
|
103
|
+
|
|
104
|
+
isLoading = false
|
|
105
|
+
await accountInfoChanged?.()
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error(err)
|
|
108
|
+
await error?.({
|
|
109
|
+
heading: translate('configuration.messageHeading.failedToGenerateNewPIN', 'Failed To Generate New PIN'),
|
|
110
|
+
message:
|
|
111
|
+
err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'),
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function copyTextToClipboard() {
|
|
118
|
+
if (activationPINInput) {
|
|
119
|
+
navigator.clipboard.writeText(activationPINInput.value)
|
|
120
|
+
await success?.({
|
|
121
|
+
heading: translate('configuration.messageHeading.activationPINCopied', 'Activation PIN Copied!'),
|
|
122
|
+
message: translate(
|
|
123
|
+
'configuration.user.permissions.activationPINCopied',
|
|
124
|
+
'Activation PIN copied to clipboard successfully.',
|
|
125
|
+
),
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function reactivateUserAccount() {
|
|
131
|
+
if (
|
|
132
|
+
confirm(
|
|
133
|
+
translate(
|
|
134
|
+
'configuration.user.permissions.reactivateUserConfirmation',
|
|
135
|
+
'Are you sure you want to reactivate this user?',
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
) {
|
|
139
|
+
userAccount.status = 'ACTIVE'
|
|
140
|
+
userAccount.lockNotes = null
|
|
141
|
+
await accountInfoChanged?.()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const emailAddressRegex =
|
|
146
|
+
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
|
147
|
+
const emailIsValid = (emailAddress: string): boolean => emailAddressRegex.test(emailAddress)
|
|
148
|
+
|
|
149
|
+
let workEmail = $derived(userAccount.workEmail)
|
|
150
|
+
let status = $derived(userAccount.status)
|
|
151
|
+
let activationPIN = $derived(userAccount.userActivationData?.activationPIN)
|
|
152
|
+
let isCreatingNewUser = $derived(userAccount.id === null)
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<div
|
|
156
|
+
class="card"
|
|
157
|
+
bind:offsetHeight={cardHeight}
|
|
158
|
+
{...rest}
|
|
159
|
+
>
|
|
160
|
+
<fieldset disabled={!canEditAccountInfo}>
|
|
161
|
+
<div class="card-header">
|
|
162
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
163
|
+
<h5 class="mb-0">
|
|
164
|
+
<Icon
|
|
165
|
+
{icon}
|
|
166
|
+
class="mr-1 me-1"
|
|
167
|
+
/>
|
|
168
|
+
{isCreatingNewUser ? translate('configuration.user.creatingNewAccountInfoHeader', 'New Account') : cardTitle}
|
|
169
|
+
</h5>
|
|
170
|
+
{#if !myAccountMode && !isCreatingNewUser && (status === 'ACTIVE' || status === 'PENDING_ACTIVATION')}
|
|
171
|
+
<Button
|
|
172
|
+
size="xs"
|
|
173
|
+
outline
|
|
174
|
+
color="danger"
|
|
175
|
+
iconClass="xmark"
|
|
176
|
+
onclick={() => deactivateUserModal?.open(userAccount)}
|
|
177
|
+
disabled={!canEditAccountInfo || !canToggleActive}
|
|
178
|
+
>
|
|
179
|
+
<span>{translate('common:deactivate', 'Deactivate')}</span>
|
|
180
|
+
</Button>
|
|
181
|
+
{:else if !myAccountMode && ((!isCreatingNewUser && status === 'DEACTIVATED') || status === 'LOCKED')}
|
|
182
|
+
<Button
|
|
183
|
+
size="xs"
|
|
184
|
+
outline
|
|
185
|
+
color="success"
|
|
186
|
+
iconClass="check"
|
|
187
|
+
onclick={() => reactivateUserAccount()}
|
|
188
|
+
disabled={!canEditAccountInfo || !canToggleActive}
|
|
189
|
+
>
|
|
190
|
+
<span>{translate('common:activate', 'Activate')}</span>
|
|
191
|
+
</Button>
|
|
192
|
+
{/if}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="card-body">
|
|
196
|
+
<div class="row">
|
|
197
|
+
{#if !isCreatingNewUser && status === 'PENDING_ACTIVATION'}
|
|
198
|
+
<div class="col-12">
|
|
199
|
+
{#if activationPIN && workEmail}
|
|
200
|
+
<div class="alert alert-info mb-0">
|
|
201
|
+
{translate(
|
|
202
|
+
'configuration.user.accountInfo.activationPINSent',
|
|
203
|
+
'An activation PIN has been sent to {{email}}.',
|
|
204
|
+
{ email: workEmail },
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
{:else}
|
|
208
|
+
<label for="activationPIN">{translate('configuration.user.activationPIN', 'Activation PIN')}</label>
|
|
209
|
+
<div class="input-group input-group-sm">
|
|
210
|
+
<input
|
|
211
|
+
bind:this={activationPINInput}
|
|
212
|
+
type="text"
|
|
213
|
+
class="form-control"
|
|
214
|
+
placeholder="###-###"
|
|
215
|
+
value={activationPIN}
|
|
216
|
+
readonly
|
|
217
|
+
/>
|
|
218
|
+
<div class="input-group-append">
|
|
219
|
+
<!-- When this button is hit, the function will call an API endpoint that will write the new PIN into the db -->
|
|
220
|
+
<!-- Therefore, this button will ignore the save function, as we need the new PIN to display it here -->
|
|
221
|
+
<Button
|
|
222
|
+
size="sm"
|
|
223
|
+
outline
|
|
224
|
+
{isLoading}
|
|
225
|
+
iconClass="refresh"
|
|
226
|
+
onclick={() => getNewActivationPIN()}
|
|
227
|
+
title={translate('configuration.user.generateNewActivationPIN', 'Generate New PIN')}
|
|
228
|
+
/>
|
|
229
|
+
<Button
|
|
230
|
+
size="sm"
|
|
231
|
+
outline
|
|
232
|
+
iconClass="copy"
|
|
233
|
+
onclick={() => copyTextToClipboard()}
|
|
234
|
+
disabled={!activationPIN}
|
|
235
|
+
title={translate('configuration.user.copyActivationPIN', 'Copy Activation PIN')}
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
{/if}
|
|
240
|
+
{#if activationPIN && userAccount.userActivationData?.activationPINExpiration}
|
|
241
|
+
<small class="text-danger"
|
|
242
|
+
>{translate('configuration.user.activationPINExpireText', 'Activation PIN expires on {{- date}}', {
|
|
243
|
+
date: userAccount.userActivationData.activationPINExpiration.toLocaleString(),
|
|
244
|
+
})}</small
|
|
245
|
+
>
|
|
246
|
+
{/if}
|
|
247
|
+
</div>
|
|
248
|
+
{/if}
|
|
249
|
+
<div class="col-12 col-lg-6">
|
|
250
|
+
<Input
|
|
251
|
+
label={translate('configuration.user.accountInfo.username', 'Username')}
|
|
252
|
+
bind:value={userAccount.name}
|
|
253
|
+
maxlength={320}
|
|
254
|
+
required={isCreatingNewUser}
|
|
255
|
+
validation={{
|
|
256
|
+
validator: value => {
|
|
257
|
+
if (!value) return 'Username is required.'
|
|
258
|
+
else return value.length > 320 ? 'Username must be less than 320 characters.' : true
|
|
259
|
+
},
|
|
260
|
+
}}
|
|
261
|
+
bind:input={usernameInput}
|
|
262
|
+
readonly={myAccountMode || undefined}
|
|
263
|
+
tabindex={myAccountMode ? -1 : undefined}
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="col-12 col-lg-6"></div>
|
|
267
|
+
<div class="col-12 col-md-6">
|
|
268
|
+
<Input
|
|
269
|
+
label={translate('configuration.user.accountInfo.firstName', 'First Name')}
|
|
270
|
+
bind:value={userAccount.firstName}
|
|
271
|
+
maxlength={100}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="col-12 col-md-6">
|
|
275
|
+
<Input
|
|
276
|
+
label={translate('configuration.user.accountInfo.lastName', 'Last Name')}
|
|
277
|
+
bind:value={userAccount.lastName}
|
|
278
|
+
maxlength={100}
|
|
279
|
+
/>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="col-12 col-lg-6">
|
|
282
|
+
<Input
|
|
283
|
+
label={translate('configuration.user.accountInfo.workEmail', 'Work Email')}
|
|
284
|
+
bind:value={userAccount.workEmail}
|
|
285
|
+
onchange={() => {
|
|
286
|
+
workEmailIsValid = !myAccountMode && userAccount.workEmail ? emailIsValid(userAccount.workEmail) : true
|
|
287
|
+
}}
|
|
288
|
+
autocomplete="email"
|
|
289
|
+
type="email"
|
|
290
|
+
inputmode="email"
|
|
291
|
+
readonly={myAccountMode || undefined}
|
|
292
|
+
tabindex={myAccountMode ? -1 : undefined}
|
|
293
|
+
hint={!workEmailIsValid ? translate('common:invalidEmailAddress', 'Invalid Email') : undefined}
|
|
294
|
+
hintClass="text-danger"
|
|
295
|
+
/>
|
|
296
|
+
{#if workEmail && !isCreatingNewUser && status === 'PENDING_ACTIVATION'}
|
|
297
|
+
<Button
|
|
298
|
+
size="sm"
|
|
299
|
+
color="link"
|
|
300
|
+
class="p-0 mb-2"
|
|
301
|
+
onclick={() => getNewActivationPIN(true)}
|
|
302
|
+
>
|
|
303
|
+
{translate('configuration.user.sendNewActivationPIN', 'Send New Activation PIN')}...
|
|
304
|
+
</Button>
|
|
305
|
+
{/if}
|
|
306
|
+
</div>
|
|
307
|
+
<div class="col-12 col-lg-6">
|
|
308
|
+
<Input
|
|
309
|
+
label={translate(
|
|
310
|
+
'configuration.user.accountInfo.passwordRecoveryModal.passwordRecoveryEmail',
|
|
311
|
+
'Password Recovery Email',
|
|
312
|
+
)}
|
|
313
|
+
bind:value={userAccount.recoveryEmail}
|
|
314
|
+
onchange={() => {
|
|
315
|
+
recoveryEmailIsValid =
|
|
316
|
+
myAccountMode && userAccount.recoveryEmail ? emailIsValid(userAccount.recoveryEmail) : true
|
|
317
|
+
}}
|
|
318
|
+
autocomplete="email"
|
|
319
|
+
type="email"
|
|
320
|
+
inputmode="email"
|
|
321
|
+
readonly={!myAccountMode || undefined}
|
|
322
|
+
tabindex={!myAccountMode ? -1 : undefined}
|
|
323
|
+
hint={!recoveryEmailIsValid ? translate('common:invalidEmailAddress', 'Invalid Email') : undefined}
|
|
324
|
+
hintClass="text-danger"
|
|
325
|
+
/>
|
|
326
|
+
</div>
|
|
327
|
+
{#if !isCreatingNewUser && !myAccountMode}
|
|
328
|
+
<div class="col-12">
|
|
329
|
+
<TextArea
|
|
330
|
+
label={translate('configuration.user.accountInfo.lockNote', 'Lock Note')}
|
|
331
|
+
labelClass="py-0 mb-2"
|
|
332
|
+
style="min-height:83px;"
|
|
333
|
+
bind:value={userAccount.lockNotes}
|
|
334
|
+
readonly
|
|
335
|
+
/>
|
|
336
|
+
</div>
|
|
337
|
+
{/if}
|
|
338
|
+
{@render formFields?.()}
|
|
339
|
+
</div>
|
|
340
|
+
{@render children?.()}
|
|
341
|
+
</div>
|
|
342
|
+
<div class="card-footer">
|
|
343
|
+
{#if !myAccountMode}
|
|
344
|
+
<Button
|
|
345
|
+
size="sm"
|
|
346
|
+
outline
|
|
347
|
+
iconClass="paper-plane"
|
|
348
|
+
disabled={!canEditAccountInfo}
|
|
349
|
+
onclick={() => {
|
|
350
|
+
passwordRecoveryModal?.open(userAccount.recoveryEmail, userAccount.lastPasswordResetDate)
|
|
351
|
+
}}
|
|
352
|
+
>
|
|
353
|
+
{translate('configuration.user.sendResetToken', 'Send Reset Token')}...
|
|
354
|
+
</Button>
|
|
355
|
+
{/if}
|
|
356
|
+
{#if (!myAccountMode && hasPermissionToChangePassword) || (myAccountMode && !userAccount.newPassword)}
|
|
357
|
+
<Button
|
|
358
|
+
size="sm"
|
|
359
|
+
outline
|
|
360
|
+
iconClass="key"
|
|
361
|
+
disabled={!myAccountMode && !canEditAccountInfo}
|
|
362
|
+
onclick={() => passwordSetModal?.open(userAccount)}
|
|
363
|
+
>
|
|
364
|
+
{#if myAccountMode}
|
|
365
|
+
{translate('configuration.user.changePassword', 'Change Password')}...
|
|
366
|
+
{:else}
|
|
367
|
+
{translate('configuration.user.setPassword', 'Set Password')}...
|
|
368
|
+
{/if}
|
|
369
|
+
</Button>
|
|
370
|
+
{:else if myAccountMode && userAccount.newPassword}
|
|
371
|
+
<Button
|
|
372
|
+
outline
|
|
373
|
+
color="danger"
|
|
374
|
+
onclick={() => {
|
|
375
|
+
userAccount.currentPassword = ''
|
|
376
|
+
userAccount.newPassword = ''
|
|
377
|
+
}}
|
|
378
|
+
>
|
|
379
|
+
{translate('configuration.user.cancelPasswordChange', 'Cancel Password Change')}</Button
|
|
380
|
+
>
|
|
381
|
+
{/if}
|
|
382
|
+
</div>
|
|
383
|
+
</fieldset>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
<PasswordRecoveryModal
|
|
387
|
+
bind:this={passwordRecoveryModal}
|
|
388
|
+
bind:doSendPasswordRecoveryToken
|
|
389
|
+
{sendPasswordRecoveryToken}
|
|
390
|
+
/>
|
|
391
|
+
|
|
392
|
+
<DeactivateUserModal
|
|
393
|
+
bind:this={deactivateUserModal}
|
|
394
|
+
deactivateUser={async ({ lockNotes }) => {
|
|
395
|
+
userAccount.status = 'DEACTIVATED'
|
|
396
|
+
userAccount.lockNotes = lockNotes
|
|
397
|
+
try {
|
|
398
|
+
await deactivateUser?.({ id: userAccount.id, lockNotes })
|
|
399
|
+
await accountInfoChanged?.()
|
|
400
|
+
} catch (err) {
|
|
401
|
+
console.error(err)
|
|
402
|
+
await error?.({
|
|
403
|
+
heading: translate('configuration.user.messageHeading.failedToDeactivateUser', 'Failed To Deactivate User'),
|
|
404
|
+
message: err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'),
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
}}
|
|
408
|
+
/>
|
|
409
|
+
|
|
410
|
+
<PasswordSetModal
|
|
411
|
+
bind:this={passwordSetModal}
|
|
412
|
+
changePasswordMode={myAccountMode}
|
|
413
|
+
confirmPasswordSet={async ({ currentPassword, newPassword }) => {
|
|
414
|
+
if (hasPermissionToChangePassword || myAccountMode) {
|
|
415
|
+
if (confirmPasswordSet) {
|
|
416
|
+
try {
|
|
417
|
+
await confirmPasswordSet({ currentPassword, newPassword })
|
|
418
|
+
await success?.({
|
|
419
|
+
heading: translate('configuration.user.passwordChangeSuccessHeading', 'Password Changed!'),
|
|
420
|
+
message: translate('configuration.user.passwordChangeSuccessMessage', 'Password changed successfully'),
|
|
421
|
+
})
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error(err)
|
|
424
|
+
await error?.({
|
|
425
|
+
heading: translate('configuration.user.passwordChangeErrorHeading', 'Failed To Change Password'),
|
|
426
|
+
message:
|
|
427
|
+
err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'),
|
|
428
|
+
})
|
|
429
|
+
throw err
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
//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
|
|
433
|
+
userAccount.currentPassword = currentPassword
|
|
434
|
+
userAccount.newPassword = newPassword
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}}
|
|
438
|
+
validationRules={passwordValidationRules}
|
|
439
|
+
/>
|