@isoftdata/svelte-user-configuration
Advanced tools
@@ -1,19 +0,31 @@ | ||
| <script>import { getContext, tick } from "svelte"; | ||
| import { klona } from "klona"; | ||
| import { translate as defaultTranslate } from "@isoftdata/utility-string"; | ||
| import Modal from "@isoftdata/svelte-modal"; | ||
| import TextArea from "@isoftdata/svelte-textarea"; | ||
| let show = false; | ||
| let userAccount; | ||
| let lockNotes; | ||
| let textarea; | ||
| export let deactivateUser = void 0; | ||
| export async function open(userAccountCtx) { | ||
| userAccount = klona(userAccountCtx); | ||
| lockNotes = userAccount.lockNotes; | ||
| show = true; | ||
| await tick(); | ||
| textarea?.select(); | ||
| } | ||
| const { t: translate } = getContext("i18next") || { t: defaultTranslate }; | ||
| <script lang="ts"> | ||
| import type { UserAccount } from './' | ||
| import type { i18n } from 'i18next' | ||
| import { getContext, tick } from 'svelte' | ||
| import { klona } from 'klona' | ||
| import { translate as defaultTranslate } from '@isoftdata/utility-string' | ||
| import Modal from '@isoftdata/svelte-modal' | ||
| import TextArea from '@isoftdata/svelte-textarea' | ||
| let show: boolean = $state(false) | ||
| let userAccount: UserAccount | undefined = $state() | ||
| let lockNotes: UserAccount['lockNotes'] = $state(null) | ||
| let textarea: HTMLTextAreaElement | undefined = $state() | ||
| interface Props { | ||
| deactivateUser?: ((user: { id: UserAccount['id']; lockNotes: UserAccount['lockNotes'] }) => void | Promise<void>) | undefined; | ||
| } | ||
| let { deactivateUser = undefined }: Props = $props(); | ||
| export async function open(userAccountCtx: UserAccount) { | ||
| userAccount = klona(userAccountCtx) | ||
| lockNotes = userAccount.lockNotes | ||
| show = true | ||
| await tick() | ||
| textarea?.select() | ||
| } | ||
| const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate } | ||
| </script> | ||
@@ -28,3 +40,3 @@ | ||
| confirmButtonFormId="deactivateUserModalForm" | ||
| on:close={() => (show = false)} | ||
| close={() => (show = false)} | ||
| bind:show | ||
@@ -34,3 +46,3 @@ > | ||
| id="deactivateUserModalForm" | ||
| on:submit={async e => { | ||
| onsubmit={async e => { | ||
| e.preventDefault() | ||
@@ -37,0 +49,0 @@ userAccount ? await deactivateUser?.({ id: userAccount.id, lockNotes }) : undefined |
@@ -1,24 +0,12 @@ | ||
| import { SvelteComponent } from "svelte"; | ||
| import type { UserAccount } from './'; | ||
| declare const __propDef: { | ||
| props: { | ||
| deactivateUser?: ((user: { | ||
| id: UserAccount["id"]; | ||
| lockNotes: UserAccount["lockNotes"]; | ||
| }) => void | Promise<void>) | undefined; | ||
| open?: (userAccountCtx: UserAccount) => Promise<void>; | ||
| }; | ||
| events: { | ||
| [evt: string]: CustomEvent<any>; | ||
| }; | ||
| slots: {}; | ||
| exports?: {} | undefined; | ||
| bindings?: string | undefined; | ||
| }; | ||
| export type DeactivateUserModalProps = typeof __propDef.props; | ||
| export type DeactivateUserModalEvents = typeof __propDef.events; | ||
| export type DeactivateUserModalSlots = typeof __propDef.slots; | ||
| export default class DeactivateUserModal extends SvelteComponent<DeactivateUserModalProps, DeactivateUserModalEvents, DeactivateUserModalSlots> { | ||
| get open(): (userAccountCtx: UserAccount) => Promise<void>; | ||
| interface Props { | ||
| deactivateUser?: ((user: { | ||
| id: UserAccount['id']; | ||
| lockNotes: UserAccount['lockNotes']; | ||
| }) => void | Promise<void>) | undefined; | ||
| } | ||
| export {}; | ||
| declare const DeactivateUserModal: import("svelte").Component<Props, { | ||
| open: (userAccountCtx: UserAccount) => Promise<void>; | ||
| }, "">; | ||
| type DeactivateUserModal = ReturnType<typeof DeactivateUserModal>; | ||
| export default DeactivateUserModal; |
@@ -1,16 +0,26 @@ | ||
| <script>import { getContext } from "svelte"; | ||
| import Modal from "@isoftdata/svelte-modal"; | ||
| import Input from "@isoftdata/svelte-input"; | ||
| import { translate as defaultTranslate } from "@isoftdata/utility-string"; | ||
| const { t: translate } = getContext("i18next") || { t: defaultTranslate }; | ||
| export let sendPasswordRecoveryToken = void 0; | ||
| export let doSendPasswordRecoveryToken; | ||
| let resetDate = void 0; | ||
| let email = null; | ||
| let show = false; | ||
| export function open(passwordRecoveryEmail, lastResetDate) { | ||
| email = passwordRecoveryEmail; | ||
| resetDate = lastResetDate; | ||
| show = true; | ||
| } | ||
| <script lang="ts"> | ||
| import type { i18n } from 'i18next' | ||
| import { getContext } from 'svelte' | ||
| import Modal from '@isoftdata/svelte-modal' | ||
| import Input from '@isoftdata/svelte-input' | ||
| import { translate as defaultTranslate } from '@isoftdata/utility-string' | ||
| const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate } | ||
| interface Props { | ||
| sendPasswordRecoveryToken?: ((email: string | null) => void | Promise<void>) | undefined | ||
| doSendPasswordRecoveryToken: boolean | ||
| } | ||
| let { sendPasswordRecoveryToken = undefined, doSendPasswordRecoveryToken = $bindable() }: Props = $props() | ||
| let resetDate: Date | null | undefined = $state(undefined) | ||
| let email: string | null = $state(null) | ||
| let show: boolean = $state(false) | ||
| export function open(passwordRecoveryEmail: typeof email, lastResetDate: Date | null | undefined) { | ||
| email = passwordRecoveryEmail | ||
| resetDate = lastResetDate | ||
| show = true | ||
| } | ||
| </script> | ||
@@ -21,6 +31,9 @@ | ||
| title={translate('configuration.user.accountInfo.passwordRecovery.header', 'Password Recovery')} | ||
| confirmButtonText={translate('configuration.user.accountInfo.passwordManagementModal.requestResetToken', 'Request Reset Token')} | ||
| confirmButtonText={translate( | ||
| 'configuration.user.accountInfo.passwordManagementModal.requestResetToken', | ||
| 'Request Reset Token', | ||
| )} | ||
| confirmButtonIcon="check" | ||
| confirmButtonDisabled={!email} | ||
| on:close={() => (show = false)} | ||
| close={() => (show = false)} | ||
| confirmButtonType="submit" | ||
@@ -31,3 +44,3 @@ confirmButtonFormId="passwordRecoveryForm" | ||
| id="passwordRecoveryForm" | ||
| on:submit={async event => { | ||
| onsubmit={async event => { | ||
| event.preventDefault() | ||
@@ -43,5 +56,8 @@ try { | ||
| > | ||
| <div class="form-row"> | ||
| <div class="row"> | ||
| <Input | ||
| label={translate('configuration.user.accountInfo.passwordManagementModal.lastPasswordSetDate', 'Last Password Set Date')} | ||
| label={translate( | ||
| 'configuration.user.accountInfo.passwordManagementModal.lastPasswordSetDate', | ||
| 'Last Password Set Date', | ||
| )} | ||
| value={resetDate?.toLocaleString() ?? ''} | ||
@@ -52,3 +68,6 @@ labelParentClass="col-12 col-md-6 form-group mb-0" | ||
| <Input | ||
| label={translate('configuration.user.accountInfo.passwordRecoveryModal.passwordRecoveryEmail', 'Password Recovery Email')} | ||
| label={translate( | ||
| 'configuration.user.accountInfo.passwordRecoveryModal.passwordRecoveryEmail', | ||
| 'Password Recovery Email', | ||
| )} | ||
| value={email ?? ''} | ||
@@ -60,3 +79,6 @@ labelParentClass="col-12 col-md-6 form-group mb-0" | ||
| <div class="alert alert-danger mt-3 mb-0"> | ||
| {translate('configuration.user.accountInfo.passwordRecovery.noRecoveryEmail', 'No recovery email has been set for this user.')} | ||
| {translate( | ||
| 'configuration.user.accountInfo.passwordRecovery.noRecoveryEmail', | ||
| 'No recovery email has been set for this user.', | ||
| )} | ||
| </div> | ||
@@ -63,0 +85,0 @@ {/if} |
@@ -1,21 +0,9 @@ | ||
| import { SvelteComponent } from "svelte"; | ||
| declare const __propDef: { | ||
| props: { | ||
| sendPasswordRecoveryToken?: ((email: string | null) => void | Promise<void>) | undefined; | ||
| doSendPasswordRecoveryToken: boolean; | ||
| open?: (passwordRecoveryEmail: string | null, lastResetDate: Date | null | undefined) => void; | ||
| }; | ||
| events: { | ||
| [evt: string]: CustomEvent<any>; | ||
| }; | ||
| slots: {}; | ||
| exports?: {} | undefined; | ||
| bindings?: string | undefined; | ||
| }; | ||
| export type PasswordRecoveryModalProps = typeof __propDef.props; | ||
| export type PasswordRecoveryModalEvents = typeof __propDef.events; | ||
| export type PasswordRecoveryModalSlots = typeof __propDef.slots; | ||
| export default class PasswordRecoveryModal extends SvelteComponent<PasswordRecoveryModalProps, PasswordRecoveryModalEvents, PasswordRecoveryModalSlots> { | ||
| get open(): (passwordRecoveryEmail: string | null, lastResetDate: Date | null | undefined) => void; | ||
| interface Props { | ||
| sendPasswordRecoveryToken?: ((email: string | null) => void | Promise<void>) | undefined; | ||
| doSendPasswordRecoveryToken: boolean; | ||
| } | ||
| export {}; | ||
| declare const PasswordRecoveryModal: import("svelte").Component<Props, { | ||
| open: (passwordRecoveryEmail: string | null, lastResetDate: Date | null | undefined) => void; | ||
| }, "doSendPasswordRecoveryToken">; | ||
| type PasswordRecoveryModal = ReturnType<typeof PasswordRecoveryModal>; | ||
| export default PasswordRecoveryModal; |
@@ -1,37 +0,57 @@ | ||
| <script>import { klona } from "klona"; | ||
| import { tick, getContext } from "svelte"; | ||
| import Input from "@isoftdata/svelte-input"; | ||
| import Modal from "@isoftdata/svelte-modal"; | ||
| import PasswordFields from "@isoftdata/svelte-password-fields"; | ||
| import { translate as defaultTranslate } from "@isoftdata/utility-string"; | ||
| let show = false; | ||
| let currentPassword = ""; | ||
| let newPassword = ""; | ||
| let confirmPassword = ""; | ||
| let passwordMismatch = false; | ||
| let lastResetDate = void 0; | ||
| let currentPasswordInput = void 0; | ||
| let newPasswordInput = void 0; | ||
| let passwordRecoveryEmail = void 0; | ||
| let passwordIsValid = false; | ||
| export let validationRules = void 0; | ||
| export let changePasswordMode = false; | ||
| export let confirmPasswordSet; | ||
| export async function open(userAccount) { | ||
| currentPassword = ""; | ||
| newPassword = ""; | ||
| confirmPassword = ""; | ||
| lastResetDate = klona(userAccount?.lastPasswordResetDate); | ||
| passwordRecoveryEmail = userAccount?.recoveryEmail; | ||
| show = true; | ||
| await tick(); | ||
| if (changePasswordMode) { | ||
| currentPasswordInput?.focus(); | ||
| } else { | ||
| newPasswordInput?.focus(); | ||
| } | ||
| } | ||
| const { t: translate } = getContext("i18next") || { t: defaultTranslate }; | ||
| $: passwordMismatch = !(newPassword && confirmPassword && newPassword === confirmPassword); | ||
| $: disablePasswordChange = changePasswordMode && !currentPassword || passwordMismatch || !newPassword || !confirmPassword || !passwordIsValid; | ||
| <script lang="ts"> | ||
| import type { i18n } from 'i18next' | ||
| import type { UserAccount, PasswordValidationRules } from './' | ||
| import { klona } from 'klona' | ||
| import { tick, getContext } from 'svelte' | ||
| import Input from '@isoftdata/svelte-input' | ||
| import Modal from '@isoftdata/svelte-modal' | ||
| import PasswordFields from '@isoftdata/svelte-password-fields' | ||
| import { translate as defaultTranslate } from '@isoftdata/utility-string' | ||
| let show: boolean = $state(false) | ||
| let currentPassword: string = $state('') | ||
| let newPassword: string = $state('') | ||
| let confirmPassword: string = $state('') | ||
| let lastResetDate: Date | null | undefined = $state(undefined) | ||
| let currentPasswordInput: HTMLInputElement | undefined = $state(undefined) | ||
| let newPasswordInput: HTMLInputElement | undefined = $state(undefined) | ||
| let passwordRecoveryEmail: string | null | undefined = $state(undefined) | ||
| let passwordIsValid: boolean = $state(false) | ||
| interface Props { | ||
| validationRules?: PasswordValidationRules | undefined | ||
| changePasswordMode?: boolean | ||
| confirmPasswordSet: (ctx: { currentPassword: string; newPassword: string }) => void | Promise<void> | ||
| } | ||
| let { validationRules = undefined, changePasswordMode = false, confirmPasswordSet }: Props = $props() | ||
| export async function open(userAccount?: UserAccount) { | ||
| currentPassword = '' | ||
| newPassword = '' | ||
| confirmPassword = '' | ||
| lastResetDate = klona(userAccount?.lastPasswordResetDate) // Don't mutate the given Date obj | ||
| passwordRecoveryEmail = userAccount?.recoveryEmail | ||
| show = true | ||
| await tick() | ||
| if (changePasswordMode) { | ||
| currentPasswordInput?.focus() | ||
| } else { | ||
| newPasswordInput?.focus() | ||
| } | ||
| } | ||
| const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate } | ||
| // The passwordMismatch prop from the PasswordFields component only updates on:change, | ||
| // but we want the component consumer to be able to update their save button disabled state in real time | ||
| let passwordMismatch: boolean = $derived(!(newPassword && confirmPassword && newPassword === confirmPassword)) | ||
| let disablePasswordChange: boolean = $derived( | ||
| (changePasswordMode && !currentPassword) || | ||
| passwordMismatch || | ||
| !newPassword || | ||
| !confirmPassword || | ||
| !passwordIsValid, | ||
| ) | ||
| </script> | ||
@@ -41,7 +61,11 @@ | ||
| bind:show | ||
| title={changePasswordMode ? translate('configuration.user.changePassword', 'Change Password') : translate('configuration.user.setPassword', 'Set Password')} | ||
| confirmButtonText={changePasswordMode ? translate('configuration.user.changePassword', 'Change Password') : translate('configuration.user.setPassword', 'Set Password')} | ||
| title={changePasswordMode | ||
| ? translate('configuration.user.changePassword', 'Change Password') | ||
| : translate('configuration.user.setPassword', 'Set Password')} | ||
| confirmButtonText={changePasswordMode | ||
| ? translate('configuration.user.changePassword', 'Change Password') | ||
| : translate('configuration.user.setPassword', 'Set Password')} | ||
| confirmButtonIcon="check" | ||
| confirmButtonDisabled={disablePasswordChange} | ||
| on:close={() => (show = false)} | ||
| close={() => (show = false)} | ||
| confirmButtonType="submit" | ||
@@ -52,3 +76,3 @@ confirmButtonFormId="setPasswordForm" | ||
| id="setPasswordForm" | ||
| on:submit={async e => { | ||
| onsubmit={async e => { | ||
| e.preventDefault() | ||
@@ -62,3 +86,6 @@ if (!disablePasswordChange) { | ||
| <Input | ||
| label={translate('configuration.user.accountInfo.passwordManagementModal.lastPasswordSetDate', 'Last Password Set Date')} | ||
| label={translate( | ||
| 'configuration.user.accountInfo.passwordManagementModal.lastPasswordSetDate', | ||
| 'Last Password Set Date', | ||
| )} | ||
| tabindex={-1} | ||
@@ -89,7 +116,13 @@ value={lastResetDate ? lastResetDate.toLocaleString() : ''} | ||
| passwordLabel={translate('configuration.user.accountInfo.passwordManagementModal.newPassword', 'New Password')} | ||
| confirmPasswordLabel={translate('configuration.user.accountInfo.passwordManagementModal.confirmNewPassword', 'Confirm New Password')} | ||
| confirmPasswordLabel={translate( | ||
| 'configuration.user.accountInfo.passwordManagementModal.confirmNewPassword', | ||
| 'Confirm New Password', | ||
| )} | ||
| /> | ||
| {#if !passwordRecoveryEmail} | ||
| <div class="alert alert-danger mt-3 mb-0"> | ||
| {translate('configuration.user.accountInfo.passwordRecovery.noRecoveryEmail', 'No recovery email has been set for this user.')} | ||
| {translate( | ||
| 'configuration.user.accountInfo.passwordRecovery.noRecoveryEmail', | ||
| 'No recovery email has been set for this user.', | ||
| )} | ||
| </div> | ||
@@ -96,0 +129,0 @@ {/if} |
@@ -1,26 +0,14 @@ | ||
| import { SvelteComponent } from "svelte"; | ||
| import type { UserAccount, PasswordValidationRules } from './'; | ||
| declare const __propDef: { | ||
| props: { | ||
| validationRules?: PasswordValidationRules | undefined; | ||
| changePasswordMode?: boolean; | ||
| confirmPasswordSet: (ctx: { | ||
| currentPassword: string; | ||
| newPassword: string; | ||
| }) => void | Promise<void>; | ||
| open?: (userAccount?: UserAccount) => Promise<void>; | ||
| }; | ||
| events: { | ||
| [evt: string]: CustomEvent<any>; | ||
| }; | ||
| slots: {}; | ||
| exports?: {} | undefined; | ||
| bindings?: string | undefined; | ||
| }; | ||
| export type PasswordSetModalProps = typeof __propDef.props; | ||
| export type PasswordSetModalEvents = typeof __propDef.events; | ||
| export type PasswordSetModalSlots = typeof __propDef.slots; | ||
| export default class PasswordSetModal extends SvelteComponent<PasswordSetModalProps, PasswordSetModalEvents, PasswordSetModalSlots> { | ||
| get open(): (userAccount?: UserAccount) => Promise<void>; | ||
| interface Props { | ||
| validationRules?: PasswordValidationRules | undefined; | ||
| changePasswordMode?: boolean; | ||
| confirmPasswordSet: (ctx: { | ||
| currentPassword: string; | ||
| newPassword: string; | ||
| }) => void | Promise<void>; | ||
| } | ||
| export {}; | ||
| declare const PasswordSetModal: import("svelte").Component<Props, { | ||
| open: (userAccount?: UserAccount) => Promise<void>; | ||
| }, "">; | ||
| type PasswordSetModal = ReturnType<typeof PasswordSetModal>; | ||
| export default PasswordSetModal; |
+229
-136
@@ -1,86 +0,169 @@ | ||
| <script>import { getContext } from "svelte"; | ||
| import camelCase from "just-camel-case"; | ||
| import Icon from "@isoftdata/svelte-icon"; | ||
| import Button from "@isoftdata/svelte-button"; | ||
| import Select from "@isoftdata/svelte-select"; | ||
| import { getEventValue } from "@isoftdata/browser-event"; | ||
| import { Table, Td } from "@isoftdata/svelte-table"; | ||
| import { translate as defaultTranslate } from "@isoftdata/utility-string"; | ||
| const { t: translate } = getContext("i18next") || { t: defaultTranslate }; | ||
| export let permissions; | ||
| export let siteLabel = "Site"; | ||
| export let permissionValueChange = void 0; | ||
| export let icon = "user-lock"; | ||
| export let cardHeaderTitle = translate("common:permissions", "Permissions"); | ||
| export let permissionValueMap = /* @__PURE__ */ new Map(); | ||
| export let groupPermissionValueMap = void 0; | ||
| let computedPermissions = []; | ||
| let selectedPermissionIds = /* @__PURE__ */ new Set(); | ||
| let selectedPermissionValue = null; | ||
| const permissionColumns = getColumns(groupPermissionValueMap); | ||
| const permissionValueList = { | ||
| NONE: { label: translate("common:permissionLevel.none", "None"), value: "NONE", color: "danger" }, | ||
| SITE: { label: translate(`common:permissionLevel.${siteLabel.toLowerCase()}`, siteLabel), value: "SITE", color: "primary" }, | ||
| GLOBAL: { label: translate("common:permissionLevel.global", "Global"), value: "GLOBAL", color: "success" } | ||
| }; | ||
| function getColumns(groupPermissionValueMap2) { | ||
| let columns = [ | ||
| { property: "value", name: groupPermissionValueMap2 ? translate("common:user", "User") : translate("common:permission", "Permission"), width: "1rem" }, | ||
| { property: "category", name: translate("common:category", "Category") }, | ||
| { property: "displayName", name: translate("common:name", "Name") } | ||
| ]; | ||
| if (groupPermissionValueMap2) { | ||
| columns.splice( | ||
| 1, | ||
| 0, | ||
| { property: "groupValue", name: translate("common:group", "Group"), align: "center", width: "1rem", minWidth: "25%" }, | ||
| { property: "computedValue", name: translate("common:access", "Access"), align: "center", width: "1rem", minWidth: "25%" } | ||
| ); | ||
| } | ||
| return columns; | ||
| } | ||
| function selectPermissions(permissionId) { | ||
| selectedPermissionIds.has(permissionId) ? selectedPermissionIds.delete(permissionId) : selectedPermissionIds.add(permissionId); | ||
| selectedPermissionIds = selectedPermissionIds; | ||
| } | ||
| function selectAndDeselectAllPermissions() { | ||
| if (selectedPermissionIds.size === computedPermissions.length) { | ||
| selectedPermissionIds.clear(); | ||
| selectedPermissionValue = null; | ||
| } else { | ||
| selectedPermissionIds = new Set(permissions.map((permission) => permission.id)); | ||
| } | ||
| selectedPermissionIds = selectedPermissionIds; | ||
| } | ||
| const getButtonColor = (optionValue, permissionValue) => optionValue === permissionValue ? permissionValueList[optionValue].color : "outline-secondary"; | ||
| const isValidPermissionValue = (value) => Object.keys(permissionValueList).includes(value); | ||
| async function updatePermissionValues(event, permissionIds) { | ||
| const newValue = getEventValue(event); | ||
| if (isValidPermissionValue(newValue)) { | ||
| const permissionIdsArray = Array.from(permissionIds); | ||
| for (const id of permissionIdsArray) { | ||
| permissionValueMap.set(id, newValue); | ||
| } | ||
| permissionValueMap = permissionValueMap; | ||
| await permissionValueChange?.({ | ||
| value: newValue, | ||
| permissionIds: permissionIdsArray | ||
| }); | ||
| } | ||
| } | ||
| function getComputedPermissions(permissions2, permissionValueMap2, groupPermissionValueMap2) { | ||
| return permissions2.map((permission) => { | ||
| const value = permissionValueMap2.get(permission.id) ?? "NONE"; | ||
| const groupValue = groupPermissionValueMap2?.get(permission.id); | ||
| return { | ||
| ...permission, | ||
| displayName: translate(`permissions:${camelCase(permission.codeName)}.displayName`, permission.displayName), | ||
| category: translate(`permissions:categories.${camelCase(permission.category)}`, permission.category), | ||
| value, | ||
| groupValue, | ||
| computedValue: value === "NONE" && groupValue || value === "SITE" && groupValue === "GLOBAL" ? groupValue : value | ||
| }; | ||
| }); | ||
| } | ||
| $: computedPermissions = getComputedPermissions(permissions, permissionValueMap, groupPermissionValueMap); | ||
| <script lang="ts"> | ||
| import type { i18n } from 'i18next' | ||
| import type { Merge } from 'type-fest' | ||
| import type { IconName, SiteLabel, HTMLDivAttributes } from './' | ||
| import type { Snippet } from 'svelte' | ||
| import { getContext } from 'svelte' | ||
| import camelCase from 'just-camel-case' | ||
| import Icon from '@isoftdata/svelte-icon' | ||
| import Button from '@isoftdata/svelte-button' | ||
| import Select from '@isoftdata/svelte-select' | ||
| import { getEventValue } from '@isoftdata/browser-event' | ||
| import { Table, Td, type Column } from '@isoftdata/svelte-table' | ||
| import { translate as defaultTranslate } from '@isoftdata/utility-string' | ||
| import { SvelteSet, SvelteMap } from 'svelte/reactivity' | ||
| const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate } | ||
| type Permission = { | ||
| id: number | ||
| displayName: string | ||
| category: string | ||
| codeName: string | ||
| } | ||
| type ComputedPermission = Merge< | ||
| Permission, | ||
| { value: PermissionValue; groupValue?: PermissionValue | undefined; computedValue?: PermissionValue } | ||
| > | ||
| type ComputedPermissionColumns = Array<Column<ComputedPermission>> | ||
| type PermissionValue = keyof typeof permissionValueList | ||
| type PermissionValueMap = Map<number, PermissionValue> | ||
| interface Props extends HTMLDivAttributes { | ||
| permissions: Array<Permission> | ||
| siteLabel?: SiteLabel | ||
| permissionValueChange?: | ||
| | ((change: { value: PermissionValue; permissionIds: Array<number> }) => void | Promise<void>) | ||
| | undefined | ||
| icon?: IconName | ||
| cardHeaderTitle?: string | ||
| permissionValueMap: PermissionValueMap | ||
| groupPermissionValueMap?: PermissionValueMap | undefined | ||
| children?: Snippet | ||
| } | ||
| let { | ||
| permissions, | ||
| siteLabel = 'Site', | ||
| permissionValueChange = undefined, | ||
| icon = 'user-lock', | ||
| cardHeaderTitle = translate('common:permissions', 'Permissions'), | ||
| permissionValueMap = $bindable(new SvelteMap()), | ||
| groupPermissionValueMap = undefined, | ||
| children, | ||
| ...rest | ||
| }: Props = $props() | ||
| let selectedPermissionIds: SvelteSet<number> = $state(new SvelteSet()) | ||
| let selectedPermissionValue: string | null = $state(null) | ||
| const permissionColumns: ComputedPermissionColumns = getColumns(groupPermissionValueMap) | ||
| const permissionValueList = { | ||
| NONE: { label: translate('common:permissionLevel.none', 'None'), value: 'NONE', color: 'danger' }, | ||
| SITE: { | ||
| label: translate(`common:permissionLevel.${siteLabel.toLowerCase()}`, siteLabel), | ||
| value: 'SITE', | ||
| color: 'primary', | ||
| }, | ||
| GLOBAL: { label: translate('common:permissionLevel.global', 'Global'), value: 'GLOBAL', color: 'success' }, | ||
| } | ||
| function getColumns(groupPermissionValueMap: PermissionValueMap | undefined): ComputedPermissionColumns { | ||
| let columns: ComputedPermissionColumns = [ | ||
| { | ||
| property: 'value', | ||
| name: groupPermissionValueMap ? translate('common:user', 'User') : translate('common:permission', 'Permission'), | ||
| width: '1rem', | ||
| }, | ||
| { property: 'category', name: translate('common:category', 'Category') }, | ||
| { property: 'displayName', name: translate('common:name', 'Name') }, | ||
| ] | ||
| if (groupPermissionValueMap) { | ||
| columns.splice( | ||
| 1, | ||
| 0, | ||
| { | ||
| property: 'groupValue', | ||
| name: translate('common:group', 'Group'), | ||
| align: 'center', | ||
| width: '1rem', | ||
| minWidth: '25%', | ||
| }, | ||
| { | ||
| property: 'computedValue', | ||
| name: translate('common:access', 'Access'), | ||
| align: 'center', | ||
| width: '1rem', | ||
| minWidth: '25%', | ||
| }, | ||
| ) | ||
| } | ||
| return columns | ||
| } | ||
| function selectPermissions(permissionId: number) { | ||
| selectedPermissionIds.has(permissionId) | ||
| ? selectedPermissionIds.delete(permissionId) | ||
| : selectedPermissionIds.add(permissionId) | ||
| } | ||
| function selectAndDeselectAllPermissions() { | ||
| if (selectedPermissionIds.size === computedPermissions.length) { | ||
| selectedPermissionIds.clear() | ||
| selectedPermissionValue = null | ||
| } else { | ||
| selectedPermissionIds = new SvelteSet(permissions.map(permission => permission.id)) | ||
| } | ||
| } | ||
| const getButtonColor = (optionValue: string, permissionValue: PermissionValue) => | ||
| optionValue === permissionValue ? permissionValueList[optionValue].color : 'outline-secondary' | ||
| const isValidPermissionValue = (value: string): value is PermissionValue => | ||
| Object.keys(permissionValueList).includes(value) | ||
| async function updatePermissionValues(event: Event, permissionIds: SvelteSet<number>) { | ||
| const newValue = getEventValue(event) | ||
| if (isValidPermissionValue(newValue)) { | ||
| const permissionIdsArray = Array.from(permissionIds) | ||
| for (const id of permissionIdsArray) { | ||
| permissionValueMap.set(id, newValue) | ||
| } | ||
| permissionValueMap = permissionValueMap | ||
| await permissionValueChange?.({ | ||
| value: newValue, | ||
| permissionIds: permissionIdsArray, | ||
| }) | ||
| } | ||
| } | ||
| function getComputedPermissions( | ||
| permissions: Array<Permission>, | ||
| permissionValueMap: PermissionValueMap, | ||
| groupPermissionValueMap: PermissionValueMap | undefined, | ||
| ): Array<ComputedPermission> { | ||
| return permissions.map(permission => { | ||
| const value = permissionValueMap.get(permission.id) ?? 'NONE' | ||
| const groupValue = groupPermissionValueMap?.get(permission.id) | ||
| return { | ||
| ...permission, | ||
| displayName: translate(`permissions:${camelCase(permission.codeName)}.displayName`, permission.displayName), | ||
| category: translate(`permissions:categories.${camelCase(permission.category)}`, permission.category), | ||
| value, | ||
| groupValue, | ||
| computedValue: | ||
| (value === 'NONE' && groupValue) || (value === 'SITE' && groupValue === 'GLOBAL') ? groupValue : value, | ||
| } | ||
| }) | ||
| } | ||
| let computedPermissions = $derived.by(() => | ||
| getComputedPermissions(permissions, permissionValueMap, groupPermissionValueMap), | ||
| ) | ||
| $inspect(computedPermissions) | ||
| </script> | ||
@@ -90,3 +173,3 @@ | ||
| class="card" | ||
| {...$$restProps} | ||
| {...rest} | ||
| > | ||
@@ -97,3 +180,3 @@ <div class="card-header"> | ||
| {icon} | ||
| class="mr-1" | ||
| class="mr-1 me-1" | ||
| /> | ||
@@ -113,47 +196,53 @@ {cardHeaderTitle} | ||
| filterEnabled | ||
| let:row={permission} | ||
| > | ||
| <tr | ||
| on:click={() => selectPermissions(permission.id)} | ||
| class:table-primary={selectedPermissionIds.has(permission.id)} | ||
| class="cursor-pointer" | ||
| > | ||
| <Td property="value"> | ||
| <div | ||
| class="btn-group btn-group-toggle" | ||
| data-toggle="buttons" | ||
| > | ||
| {#each Object.values(permissionValueList) as permissionValue} | ||
| <label class="btn btn-sm btn-{getButtonColor(permissionValue.value, permission.value)} cursor-pointer"> | ||
| <input | ||
| type="radio" | ||
| autocomplete="off" | ||
| name={permission.value} | ||
| value={permissionValue.value} | ||
| on:change={e => updatePermissionValues(e, new Set([permission.id]))} | ||
| /> | ||
| {permissionValue.label} | ||
| </label> | ||
| {/each} | ||
| </div> | ||
| </Td> | ||
| {#if groupPermissionValueMap} | ||
| <Td | ||
| property="groupValue" | ||
| class="px-4" | ||
| > | ||
| <span class="badge badge-pill badge-{permissionValueList[permission.groupValue ?? 'NONE'].color}">{permissionValueList[permission.groupValue ?? 'NONE'].label}</span> | ||
| {#snippet children({ row: permission })} | ||
| <tr | ||
| onclick={() => selectPermissions(permission.id)} | ||
| class:table-primary={selectedPermissionIds.has(permission.id)} | ||
| class="cursor-pointer" | ||
| > | ||
| <Td property="value"> | ||
| <div | ||
| class="btn-group btn-group-toggle" | ||
| data-toggle="buttons" | ||
| > | ||
| {#each Object.values(permissionValueList) as permissionValue} | ||
| <label class="btn btn-sm btn-{getButtonColor(permissionValue.value, permission.value)} cursor-pointer"> | ||
| <input | ||
| class="btn-check" | ||
| type="radio" | ||
| autocomplete="off" | ||
| name={permission.value} | ||
| value={permissionValue.value} | ||
| onchange={e => updatePermissionValues(e, new SvelteSet([permission.id]))} | ||
| /> | ||
| {permissionValue.label} | ||
| </label> | ||
| {/each} | ||
| </div> | ||
| </Td> | ||
| <Td | ||
| property="computedValue" | ||
| class="px-4" | ||
| > | ||
| <span class="badge badge-pill badge-{permissionValueList[permission.computedValue ?? 'NONE'].color}">{permissionValueList[permission.computedValue ?? 'NONE'].label}</span> | ||
| </Td> | ||
| {/if} | ||
| <Td property="category">{permission.category}</Td> | ||
| <Td property="displayName">{permission.displayName}</Td> | ||
| </tr> | ||
| {#if groupPermissionValueMap} | ||
| <Td | ||
| property="groupValue" | ||
| class="px-4" | ||
| > | ||
| <span class="badge badge-pill bg-{permissionValueList[permission.groupValue ?? 'NONE'].color}" | ||
| >{permissionValueList[permission.groupValue ?? 'NONE'].label}</span | ||
| > | ||
| </Td> | ||
| <Td | ||
| property="computedValue" | ||
| class="px-4" | ||
| > | ||
| <span class="badge badge-pill bg-{permissionValueList[permission.computedValue ?? 'NONE'].color}" | ||
| >{permissionValueList[permission.computedValue ?? 'NONE'].label}</span | ||
| > | ||
| </Td> | ||
| {/if} | ||
| <Td property="category">{permission.category}</Td> | ||
| <Td property="displayName">{permission.displayName}</Td> | ||
| </tr> | ||
| {/snippet} | ||
| </Table> | ||
| <slot></slot> | ||
| {@render children?.()} | ||
| </div> | ||
@@ -168,8 +257,8 @@ <div class="card-footer"> | ||
| emptyText={translate('common:selectValue', 'Select a Value')} | ||
| labelParentClass="form-inline mr-2" | ||
| labelClass="mr-2 p-0" | ||
| labelParentClass="form-inline mr-2 me-2" | ||
| labelClass="mr-2 me-2 p-0" | ||
| disabled={selectedPermissionIds.size === 0} | ||
| on:change={e => { | ||
| onchange={e => { | ||
| updatePermissionValues(e, selectedPermissionIds) | ||
| selectedPermissionIds = new Set() //clear the Set and trigger reactivity | ||
| selectedPermissionIds.clear() | ||
| selectedPermissionValue = null | ||
@@ -187,6 +276,10 @@ }} | ||
| iconClass="check-double" | ||
| on:click={selectAndDeselectAllPermissions} | ||
| onclick={selectAndDeselectAllPermissions} | ||
| > | ||
| <span class:d-none={selectedPermissionIds.size !== computedPermissions.length}>{translate('common:deselectAll', 'Deselect All')}</span> | ||
| <span class:d-none={selectedPermissionIds.size === computedPermissions.length}>{translate('common:selectAll', 'Select All')}</span> | ||
| <span class:d-none={selectedPermissionIds.size !== computedPermissions.length} | ||
| >{translate('common:deselectAll', 'Deselect All')}</span | ||
| > | ||
| <span class:d-none={selectedPermissionIds.size === computedPermissions.length} | ||
| >{translate('common:selectAll', 'Select All')}</span | ||
| > | ||
| </Button> | ||
@@ -193,0 +286,0 @@ </div> |
@@ -1,35 +0,22 @@ | ||
| import { SvelteComponent } from "svelte"; | ||
| import type { IconName, SiteLabel, HTMLDivAttributes } from './'; | ||
| declare const __propDef: { | ||
| props: HTMLDivAttributes & { | ||
| permissions: Array<{ | ||
| id: number; | ||
| displayName: string; | ||
| category: string; | ||
| codeName: string; | ||
| }>; | ||
| siteLabel?: SiteLabel; | ||
| permissionValueChange?: ((change: { | ||
| value: "NONE" | "SITE" | "GLOBAL"; | ||
| permissionIds: Array<number>; | ||
| }) => void | Promise<void>) | undefined; | ||
| icon?: IconName; | ||
| cardHeaderTitle?: string; | ||
| permissionValueMap: Map<number, "NONE" | "SITE" | "GLOBAL">; | ||
| groupPermissionValueMap?: Map<number, "NONE" | "SITE" | "GLOBAL"> | undefined; | ||
| }; | ||
| events: { | ||
| [evt: string]: CustomEvent<any>; | ||
| }; | ||
| slots: { | ||
| default: {}; | ||
| }; | ||
| exports?: {} | undefined; | ||
| bindings?: string | undefined; | ||
| }; | ||
| export type PermissionListProps = typeof __propDef.props; | ||
| export type PermissionListEvents = typeof __propDef.events; | ||
| export type PermissionListSlots = typeof __propDef.slots; | ||
| export default class PermissionList extends SvelteComponent<PermissionListProps, PermissionListEvents, PermissionListSlots> { | ||
| } | ||
| export {}; | ||
| import type { Snippet } from 'svelte'; | ||
| declare const PermissionList: import("svelte").Component<HTMLDivAttributes & { | ||
| permissions: Array<{ | ||
| id: number; | ||
| displayName: string; | ||
| category: string; | ||
| codeName: string; | ||
| }>; | ||
| siteLabel?: SiteLabel; | ||
| permissionValueChange?: ((change: { | ||
| value: "NONE" | "SITE" | "GLOBAL"; | ||
| permissionIds: Array<number>; | ||
| }) => void | Promise<void>) | undefined; | ||
| icon?: IconName; | ||
| cardHeaderTitle?: string; | ||
| permissionValueMap: Map<number, "NONE" | "SITE" | "GLOBAL">; | ||
| groupPermissionValueMap?: Map<number, "NONE" | "SITE" | "GLOBAL"> | undefined; | ||
| children?: Snippet; | ||
| }, {}, "permissionValueMap">; | ||
| type PermissionList = ReturnType<typeof PermissionList>; | ||
| export default PermissionList; |
+189
-96
@@ -1,79 +0,154 @@ | ||
| <script>import { getContext } from "svelte"; | ||
| import Icon from "@isoftdata/svelte-icon"; | ||
| import Input from "@isoftdata/svelte-input"; | ||
| import Button from "@isoftdata/svelte-button"; | ||
| import TextArea from "@isoftdata/svelte-textarea"; | ||
| import PasswordSetModal from "./PasswordSetModal.svelte"; | ||
| import DeactivateUserModal from "./DeactivateUserModal.svelte"; | ||
| import PasswordRecoveryModal from "./PasswordRecoveryModal.svelte"; | ||
| import { translate as defaultTranslate } from "@isoftdata/utility-string"; | ||
| const { t: translate } = getContext("i18next") || { t: defaultTranslate }; | ||
| export let userAccount; | ||
| export let icon = "user"; | ||
| export let canToggleActive = true; | ||
| export let canEditAccountInfo = true; | ||
| export let deactivateUser = void 0; | ||
| export let hasPermissionToChangePassword = false; | ||
| export let confirmPasswordSet = void 0; | ||
| export let accountInfoChanged = void 0; | ||
| export let error = void 0; | ||
| export let success = void 0; | ||
| export let sendPasswordRecoveryToken = void 0; | ||
| export let doSendPasswordRecoveryToken = false; | ||
| export let generateNewActivationPIN = () => Promise.resolve(); | ||
| export let usernameInput = void 0; | ||
| export let cardHeight = 0; | ||
| export let myAccountMode = false; | ||
| export let passwordValidationRules = void 0; | ||
| export let cardTitle = translate("configuration.user.accountInfoHeader", "Account"); | ||
| export let recoveryEmailIsValid = true; | ||
| export let workEmailIsValid = true; | ||
| let isLoading = false; | ||
| let passwordSetModal; | ||
| let deactivateUserModal; | ||
| let passwordRecoveryModal; | ||
| let activationPINInput; | ||
| async function getNewActivationPIN(sendEmail = false) { | ||
| 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?"); | ||
| if (confirm(confirmationMessage)) { | ||
| try { | ||
| isLoading = true; | ||
| await generateNewActivationPIN(userAccount.name, !!userAccount.workEmail); | ||
| let successMessage = sendEmail ? translate("configuration.user.permissions.activationPINSent", "Email with new activation PIN has been sent successfully.") : translate("configuration.user.permissions.activationPINGenerated", "Activation PIN generated successfully."); | ||
| let successHeading = sendEmail ? translate("configuration.messageHeading.activationPINSent", "New Activation PIN Sent!") : translate("configuration.messageHeading.activationPINGenerated", "New Activation PIN Generated!"); | ||
| await success?.({ heading: successHeading, message: successMessage }); | ||
| isLoading = false; | ||
| await accountInfoChanged?.(); | ||
| } catch (err) { | ||
| console.log(err); | ||
| if (err instanceof Error) { | ||
| await error?.({ heading: translate("configuration.messageHeading.failedToGenerateNewPIN", "Failed To Generate New PIN"), message: err.message }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| async function copyTextToClipboard() { | ||
| if (activationPINInput) { | ||
| navigator.clipboard.writeText(activationPINInput.value); | ||
| await success?.({ | ||
| heading: translate("configuration.messageHeading.activationPINCopied", "Activation PIN Copied!"), | ||
| message: translate("configuration.user.permissions.activationPINCopied", "Activation PIN copied to clipboard successfully.") | ||
| }); | ||
| } | ||
| } | ||
| async function reactivateUserAccount() { | ||
| if (confirm(translate("configuration.user.permissions.reactivateUserConfirmation", "Are you sure you want to reactivate this user?"))) { | ||
| userAccount.status = "ACTIVE"; | ||
| userAccount.lockNotes = null; | ||
| await accountInfoChanged?.(); | ||
| } | ||
| } | ||
| const emailAddressRegex = /^(([^<>()[\]\\.,;:\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,}))$/; | ||
| const emailIsValid = (emailAddress) => emailAddressRegex.test(emailAddress); | ||
| $: workEmail = userAccount.workEmail; | ||
| $: status = userAccount.status; | ||
| $: activationPIN = userAccount.userActivationData?.activationPIN; | ||
| $: isCreatingNewUser = userAccount.id === null; | ||
| $: recoveryEmailIsValid = myAccountMode && userAccount.recoveryEmail ? emailIsValid(userAccount.recoveryEmail) : true; | ||
| $: workEmailIsValid = !myAccountMode && userAccount.workEmail ? emailIsValid(userAccount.workEmail) : true; | ||
| <script lang="ts"> | ||
| import type { i18n } from 'i18next' | ||
| import type { HTMLDivAttributes } from './' | ||
| import type { ComponentProps, Snippet } from 'svelte' | ||
| import type { UserAccount, ConfirmPasswordSetFn, DeactivateUserFn, IconName, PasswordValidationRules } from './' | ||
| import { getContext } from 'svelte' | ||
| import Icon from '@isoftdata/svelte-icon' | ||
| import Input from '@isoftdata/svelte-input' | ||
| import Button from '@isoftdata/svelte-button' | ||
| import TextArea from '@isoftdata/svelte-textarea' | ||
| import PasswordSetModal from './PasswordSetModal.svelte' | ||
| import DeactivateUserModal from './DeactivateUserModal.svelte' | ||
| import PasswordRecoveryModal from './PasswordRecoveryModal.svelte' | ||
| import { translate as defaultTranslate } from '@isoftdata/utility-string' | ||
| const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate } | ||
| interface Props extends HTMLDivAttributes { | ||
| userAccount: UserAccount | ||
| canEditAccountInfo?: boolean | ||
| canToggleActive?: boolean | ||
| hasPermissionToChangePassword?: boolean | ||
| generateNewActivationPIN?: (userName: string, hasWorkEmail: boolean) => Promise<void> | ||
| confirmPasswordSet?: ConfirmPasswordSetFn | ||
| deactivateUser?: DeactivateUserFn | ||
| success?: ((info: { heading: string; message: string }) => void | Promise<void>) | undefined | ||
| error?: ((info: { heading: string; message: string }) => void | Promise<void>) | undefined | ||
| accountInfoChanged?: (() => void | Promise<void>) | undefined | ||
| sendPasswordRecoveryToken?: ComponentProps<typeof PasswordRecoveryModal>['sendPasswordRecoveryToken'] | ||
| doSendPasswordRecoveryToken?: boolean | ||
| icon?: IconName | ||
| usernameInput?: HTMLInputElement | undefined | ||
| cardHeight?: number | ||
| myAccountMode?: boolean | ||
| passwordValidationRules?: PasswordValidationRules | undefined | ||
| cardTitle?: string | ||
| recoveryEmailIsValid?: boolean | ||
| workEmailIsValid?: boolean | ||
| formFields?: Snippet | ||
| children?: Snippet | ||
| } | ||
| let { | ||
| userAccount = $bindable(), | ||
| canEditAccountInfo = true, | ||
| canToggleActive = true, | ||
| hasPermissionToChangePassword = false, | ||
| generateNewActivationPIN = () => Promise.resolve(), | ||
| confirmPasswordSet = undefined, | ||
| deactivateUser = undefined, | ||
| success = undefined, | ||
| error = undefined, | ||
| accountInfoChanged = undefined, | ||
| sendPasswordRecoveryToken = undefined, | ||
| doSendPasswordRecoveryToken = $bindable(false), | ||
| icon = 'user', | ||
| usernameInput = $bindable(undefined), | ||
| cardHeight = $bindable(), | ||
| myAccountMode = false, | ||
| passwordValidationRules = undefined, | ||
| cardTitle = translate('configuration.user.accountInfoHeader', 'Account'), | ||
| recoveryEmailIsValid = $bindable(true), | ||
| workEmailIsValid = $bindable(true), | ||
| formFields, | ||
| children, | ||
| ...rest | ||
| }: Props = $props() | ||
| let isLoading = $state(false) | ||
| let passwordSetModal: PasswordSetModal | undefined = $state() | ||
| let passwordRecoveryModal: PasswordRecoveryModal | undefined = $state() | ||
| let deactivateUserModal: DeactivateUserModal | undefined = $state() | ||
| let activationPINInput: HTMLInputElement | undefined = $state() | ||
| async function getNewActivationPIN(sendEmail: boolean = false) { | ||
| let confirmationMessage: string = 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?', | ||
| ) | ||
| if (confirm(confirmationMessage)) { | ||
| try { | ||
| isLoading = true | ||
| await generateNewActivationPIN(userAccount.name, !!userAccount.workEmail) | ||
| let successMessage: string = sendEmail | ||
| ? translate( | ||
| 'configuration.user.permissions.activationPINSent', | ||
| 'Email with new activation PIN has been sent successfully.', | ||
| ) | ||
| : translate('configuration.user.permissions.activationPINGenerated', 'Activation PIN generated successfully.') | ||
| let successHeading: string = sendEmail | ||
| ? translate('configuration.messageHeading.activationPINSent', 'New Activation PIN Sent!') | ||
| : translate('configuration.messageHeading.activationPINGenerated', 'New Activation PIN Generated!') | ||
| await success?.({ heading: successHeading, message: successMessage }) | ||
| isLoading = false | ||
| await accountInfoChanged?.() | ||
| } catch (err) { | ||
| console.log(err) | ||
| if (err instanceof Error) { | ||
| await error?.({ | ||
| heading: translate('configuration.messageHeading.failedToGenerateNewPIN', 'Failed To Generate New PIN'), | ||
| message: err.message, | ||
| }) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| async function copyTextToClipboard() { | ||
| if (activationPINInput) { | ||
| navigator.clipboard.writeText(activationPINInput.value) | ||
| await success?.({ | ||
| heading: translate('configuration.messageHeading.activationPINCopied', 'Activation PIN Copied!'), | ||
| message: translate( | ||
| 'configuration.user.permissions.activationPINCopied', | ||
| 'Activation PIN copied to clipboard successfully.', | ||
| ), | ||
| }) | ||
| } | ||
| } | ||
| async function reactivateUserAccount() { | ||
| if ( | ||
| confirm( | ||
| translate( | ||
| 'configuration.user.permissions.reactivateUserConfirmation', | ||
| 'Are you sure you want to reactivate this user?', | ||
| ), | ||
| ) | ||
| ) { | ||
| userAccount.status = 'ACTIVE' | ||
| userAccount.lockNotes = null | ||
| await accountInfoChanged?.() | ||
| } | ||
| } | ||
| const emailAddressRegex = | ||
| /^(([^<>()[\]\\.,;:\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,}))$/ | ||
| const emailIsValid = (emailAddress: string): boolean => emailAddressRegex.test(emailAddress) | ||
| let workEmail = $derived(userAccount.workEmail) | ||
| let status = $derived(userAccount.status) | ||
| let activationPIN = $derived(userAccount.userActivationData?.activationPIN) | ||
| let isCreatingNewUser = $derived(userAccount.id === null) | ||
| </script> | ||
@@ -84,3 +159,3 @@ | ||
| bind:offsetHeight={cardHeight} | ||
| {...$$restProps} | ||
| {...rest} | ||
| > | ||
@@ -93,3 +168,3 @@ <fieldset disabled={!canEditAccountInfo}> | ||
| {icon} | ||
| class="mr-1" | ||
| class="mr-1 me-1" | ||
| /> | ||
@@ -104,3 +179,3 @@ {isCreatingNewUser ? translate('configuration.user.creatingNewAccountInfoHeader', 'New Account') : cardTitle} | ||
| iconClass="xmark" | ||
| on:click={() => deactivateUserModal.open(userAccount)} | ||
| onclick={() => deactivateUserModal?.open(userAccount)} | ||
| disabled={!canEditAccountInfo || !canToggleActive} | ||
@@ -116,3 +191,3 @@ > | ||
| iconClass="check" | ||
| on:click={() => reactivateUserAccount()} | ||
| onclick={() => reactivateUserAccount()} | ||
| disabled={!canEditAccountInfo || !canToggleActive} | ||
@@ -126,3 +201,3 @@ > | ||
| <div class="card-body"> | ||
| <div class="form-row"> | ||
| <div class="row"> | ||
| {#if !isCreatingNewUser && status === 'PENDING_ACTIVATION'} | ||
@@ -132,3 +207,7 @@ <div class="col-12"> | ||
| <div class="alert alert-info mb-0"> | ||
| {translate('configuration.user.accountInfo.activationPINSent', 'An activation PIN has been sent to {{email}}.', { email: workEmail })} | ||
| {translate( | ||
| 'configuration.user.accountInfo.activationPINSent', | ||
| 'An activation PIN has been sent to {{email}}.', | ||
| { email: workEmail }, | ||
| )} | ||
| </div> | ||
@@ -154,3 +233,3 @@ {:else} | ||
| iconClass="refresh" | ||
| on:click={() => getNewActivationPIN()} | ||
| onclick={() => getNewActivationPIN()} | ||
| title={translate('configuration.user.generateNewActivationPIN', 'Generate New PIN')} | ||
@@ -162,3 +241,3 @@ /> | ||
| iconClass="copy" | ||
| on:click={() => copyTextToClipboard()} | ||
| onclick={() => copyTextToClipboard()} | ||
| disabled={!activationPIN} | ||
@@ -215,2 +294,5 @@ title={translate('configuration.user.copyActivationPIN', 'Copy Activation PIN')} | ||
| bind:value={userAccount.workEmail} | ||
| onchange={() => { | ||
| workEmailIsValid = !myAccountMode && userAccount.workEmail ? emailIsValid(userAccount.workEmail) : true | ||
| }} | ||
| autocomplete="email" | ||
@@ -229,3 +311,3 @@ type="email" | ||
| class="p-0 mb-2" | ||
| on:click={() => getNewActivationPIN(true)} | ||
| onclick={() => getNewActivationPIN(true)} | ||
| > | ||
@@ -238,4 +320,11 @@ {translate('configuration.user.sendNewActivationPIN', 'Send New Activation PIN')}... | ||
| <Input | ||
| label={translate('configuration.user.accountInfo.passwordRecoveryModal.passwordRecoveryEmail', 'Password Recovery Email')} | ||
| label={translate( | ||
| 'configuration.user.accountInfo.passwordRecoveryModal.passwordRecoveryEmail', | ||
| 'Password Recovery Email', | ||
| )} | ||
| bind:value={userAccount.recoveryEmail} | ||
| onchange={() => { | ||
| recoveryEmailIsValid = | ||
| myAccountMode && userAccount.recoveryEmail ? emailIsValid(userAccount.recoveryEmail) : true | ||
| }} | ||
| autocomplete="email" | ||
@@ -261,5 +350,5 @@ type="email" | ||
| {/if} | ||
| <slot name="formFields"></slot> | ||
| {@render formFields?.()} | ||
| </div> | ||
| <slot></slot> | ||
| {@render children?.()} | ||
| </div> | ||
@@ -273,4 +362,4 @@ <div class="card-footer"> | ||
| disabled={!canEditAccountInfo} | ||
| on:click={() => { | ||
| passwordRecoveryModal.open(userAccount.workEmail, userAccount.lastPasswordResetDate) | ||
| onclick={() => { | ||
| passwordRecoveryModal?.open(userAccount.workEmail, userAccount.lastPasswordResetDate) | ||
| }} | ||
@@ -287,3 +376,3 @@ > | ||
| disabled={!myAccountMode && !canEditAccountInfo} | ||
| on:click={() => passwordSetModal.open(userAccount)} | ||
| onclick={() => passwordSetModal?.open(userAccount)} | ||
| > | ||
@@ -300,3 +389,3 @@ {#if myAccountMode} | ||
| color="danger" | ||
| on:click={() => { | ||
| onclick={() => { | ||
| userAccount.currentPassword = '' | ||
@@ -330,3 +419,6 @@ userAccount.newPassword = '' | ||
| if (err instanceof Error) { | ||
| await error?.({ heading: translate('configuration.user.messageHeading.failedToDeactivateUser', 'Failed To Deactivate User'), message: err.message }) | ||
| await error?.({ | ||
| heading: translate('configuration.user.messageHeading.failedToDeactivateUser', 'Failed To Deactivate User'), | ||
| message: err.message, | ||
| }) | ||
| } | ||
@@ -352,3 +444,4 @@ } | ||
| heading: translate('configuration.user.passwordChangeErrorHeading', 'Failed To Change Password'), | ||
| message: err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'), | ||
| message: | ||
| err instanceof Error ? err.message : translate('workOrder.unknownError', 'An unknown error occurred'), | ||
| }) | ||
@@ -355,0 +448,0 @@ console.error(err) |
@@ -1,49 +0,37 @@ | ||
| import { SvelteComponent } from "svelte"; | ||
| import type { ComponentProps } from 'svelte'; | ||
| import type { UserAccount, ConfirmPasswordSetFn, DeactivateUserFn, HTMLDivAttributes, IconName, PasswordValidationRules } from './'; | ||
| import type { HTMLDivAttributes } from './'; | ||
| import type { ComponentProps, Snippet } from 'svelte'; | ||
| import type { UserAccount, ConfirmPasswordSetFn, DeactivateUserFn, IconName, PasswordValidationRules } from './'; | ||
| import PasswordRecoveryModal from './PasswordRecoveryModal.svelte'; | ||
| declare const __propDef: { | ||
| props: HTMLDivAttributes & { | ||
| userAccount: UserAccount; | ||
| canEditAccountInfo?: boolean; | ||
| canToggleActive?: boolean; | ||
| hasPermissionToChangePassword?: boolean; | ||
| generateNewActivationPIN?: (userName: string, hasWorkEmail: boolean) => Promise<void>; | ||
| confirmPasswordSet?: ConfirmPasswordSetFn; | ||
| deactivateUser?: DeactivateUserFn; | ||
| success?: ((info: { | ||
| heading: string; | ||
| message: string; | ||
| }) => void | Promise<void>) | undefined; | ||
| error?: ((info: { | ||
| heading: string; | ||
| message: string; | ||
| }) => void | Promise<void>) | undefined; | ||
| accountInfoChanged?: (() => void | Promise<void>) | undefined; | ||
| sendPasswordRecoveryToken?: ComponentProps<PasswordRecoveryModal>["sendPasswordRecoveryToken"]; | ||
| doSendPasswordRecoveryToken?: boolean; | ||
| icon?: IconName; | ||
| usernameInput?: HTMLInputElement | undefined; | ||
| cardHeight?: number; | ||
| myAccountMode?: boolean; | ||
| passwordValidationRules?: PasswordValidationRules | undefined; | ||
| cardTitle?: string; | ||
| recoveryEmailIsValid?: boolean; | ||
| workEmailIsValid?: boolean; | ||
| }; | ||
| events: { | ||
| [evt: string]: CustomEvent<any>; | ||
| }; | ||
| slots: { | ||
| formFields: {}; | ||
| default: {}; | ||
| }; | ||
| exports?: {} | undefined; | ||
| bindings?: string | undefined; | ||
| }; | ||
| export type UserAccountInfoProps = typeof __propDef.props; | ||
| export type UserAccountInfoEvents = typeof __propDef.events; | ||
| export type UserAccountInfoSlots = typeof __propDef.slots; | ||
| export default class UserAccountInfo extends SvelteComponent<UserAccountInfoProps, UserAccountInfoEvents, UserAccountInfoSlots> { | ||
| interface Props extends HTMLDivAttributes { | ||
| userAccount: UserAccount; | ||
| canEditAccountInfo?: boolean; | ||
| canToggleActive?: boolean; | ||
| hasPermissionToChangePassword?: boolean; | ||
| generateNewActivationPIN?: (userName: string, hasWorkEmail: boolean) => Promise<void>; | ||
| confirmPasswordSet?: ConfirmPasswordSetFn; | ||
| deactivateUser?: DeactivateUserFn; | ||
| success?: ((info: { | ||
| heading: string; | ||
| message: string; | ||
| }) => void | Promise<void>) | undefined; | ||
| error?: ((info: { | ||
| heading: string; | ||
| message: string; | ||
| }) => void | Promise<void>) | undefined; | ||
| accountInfoChanged?: (() => void | Promise<void>) | undefined; | ||
| sendPasswordRecoveryToken?: ComponentProps<typeof PasswordRecoveryModal>['sendPasswordRecoveryToken']; | ||
| doSendPasswordRecoveryToken?: boolean; | ||
| icon?: IconName; | ||
| usernameInput?: HTMLInputElement | undefined; | ||
| cardHeight?: number; | ||
| myAccountMode?: boolean; | ||
| passwordValidationRules?: PasswordValidationRules | undefined; | ||
| cardTitle?: string; | ||
| recoveryEmailIsValid?: boolean; | ||
| workEmailIsValid?: boolean; | ||
| formFields?: Snippet; | ||
| children?: Snippet; | ||
| } | ||
| export {}; | ||
| declare const UserAccountInfo: import("svelte").Component<Props, {}, "doSendPasswordRecoveryToken" | "userAccount" | "usernameInput" | "cardHeight" | "recoveryEmailIsValid" | "workEmailIsValid">; | ||
| type UserAccountInfo = ReturnType<typeof UserAccountInfo>; | ||
| export default UserAccountInfo; |
@@ -1,39 +0,114 @@ | ||
| <script>import UserSiteAccess from "./UserSiteAccess.svelte"; | ||
| import PermissionList from "./PermissionList.svelte"; | ||
| import UserAccountInfo from "./UserAccountInfo.svelte"; | ||
| import UserGroupMembership from "./UserGroupMembership.svelte"; | ||
| export let siteLabel = "Site"; | ||
| export let userAccountInfoIcon = "user"; | ||
| export let groupMembershipIcon = "users"; | ||
| export let permissionListIcon = "user-lock"; | ||
| export let userSiteAccessIcon = "industry-windows"; | ||
| export let permissionListCardHeaderTitle = "Permissions"; | ||
| export let usernameInput = void 0; | ||
| export let userAccount; | ||
| export let canToggleActive = true; | ||
| export let canEditAccountInfo = true; | ||
| export let doSendPasswordRecoveryToken = false; | ||
| export let hasPermissionToChangePassword = false; | ||
| export let passwordValidationRules = void 0; | ||
| export let error = void 0; | ||
| export let success = void 0; | ||
| export let deactivateUser = void 0; | ||
| export let confirmPasswordSet = void 0; | ||
| export let accountInfoChanged = void 0; | ||
| export let sendPasswordRecoveryToken = void 0; | ||
| export let generateNewActivationPIN = () => Promise.resolve(); | ||
| export let userSites; | ||
| export let canEditSiteAccess = true; | ||
| export let siteAccessChange = void 0; | ||
| export let groupMembership; | ||
| export let canEditGroupMembership = true; | ||
| export let groupMembershipChange = void 0; | ||
| export let permissions; | ||
| export let permissionValueMap = /* @__PURE__ */ new Map(); | ||
| export let groupPermissionValueMap = void 0; | ||
| export let permissionValueChange = void 0; | ||
| let cardHeight; | ||
| <script lang="ts"> | ||
| import type { | ||
| ErrorFn, | ||
| IconName, | ||
| UserSites, | ||
| SuccessFn, | ||
| SiteLabel, | ||
| UserAccount, | ||
| Permissions, | ||
| GroupMembership, | ||
| CanToggleActive, | ||
| DeactivateUserFn, | ||
| CanEditSiteAccess, | ||
| SiteAccessChangeFn, | ||
| PermissionValueMap, | ||
| CanEditAccountInfo, | ||
| ConfirmPasswordSetFn, | ||
| AccountInfoChangedFn, | ||
| CanEditGroupMembership, | ||
| PasswordValidationRules, | ||
| GroupMembershipChangeFn, | ||
| PermissionValueChangeFn, | ||
| GroupPermissionValueMap, | ||
| GenerateNewActivationPINFn, | ||
| DoSendPasswordRecoveryToken, | ||
| SendPasswordRecoveryTokenFn, | ||
| HasPermissionToChangePassword, | ||
| PermissionListCardHeaderTitle, | ||
| } from './util' | ||
| import UserSiteAccess from './UserSiteAccess.svelte' | ||
| import PermissionList from './PermissionList.svelte' | ||
| import UserAccountInfo from './UserAccountInfo.svelte' | ||
| import UserGroupMembership from './UserGroupMembership.svelte' | ||
| interface Props { | ||
| siteLabel?: SiteLabel | ||
| userAccountInfoIcon?: IconName | ||
| groupMembershipIcon?: IconName | ||
| permissionListIcon?: IconName | ||
| userSiteAccessIcon?: IconName | ||
| permissionListCardHeaderTitle?: PermissionListCardHeaderTitle | ||
| usernameInput?: HTMLInputElement | undefined | ||
| userAccount: UserAccount | ||
| canToggleActive?: CanToggleActive | ||
| canEditAccountInfo?: CanEditAccountInfo | ||
| doSendPasswordRecoveryToken?: DoSendPasswordRecoveryToken | ||
| hasPermissionToChangePassword?: HasPermissionToChangePassword | ||
| passwordValidationRules?: PasswordValidationRules | undefined | ||
| error?: ErrorFn | ||
| success?: SuccessFn | ||
| deactivateUser?: DeactivateUserFn | ||
| confirmPasswordSet?: ConfirmPasswordSetFn | undefined | ||
| accountInfoChanged?: AccountInfoChangedFn | ||
| sendPasswordRecoveryToken?: SendPasswordRecoveryTokenFn | ||
| generateNewActivationPIN?: GenerateNewActivationPINFn | ||
| userSites: UserSites | ||
| canEditSiteAccess?: CanEditSiteAccess | ||
| siteAccessChange?: SiteAccessChangeFn | ||
| groupMembership: GroupMembership | ||
| canEditGroupMembership?: CanEditGroupMembership | ||
| groupMembershipChange?: GroupMembershipChangeFn | ||
| permissions: Permissions | ||
| permissionValueMap?: PermissionValueMap | ||
| groupPermissionValueMap?: GroupPermissionValueMap | ||
| permissionValueChange?: PermissionValueChangeFn | ||
| userAccountInfo?: import('svelte').Snippet | ||
| siteAccess?: import('svelte').Snippet | ||
| userGroupMembership?: import('svelte').Snippet | ||
| permissionList?: import('svelte').Snippet | ||
| } | ||
| let { | ||
| siteLabel = 'Site', | ||
| userAccountInfoIcon = 'user', | ||
| groupMembershipIcon = 'users', | ||
| permissionListIcon = 'user-lock', | ||
| userSiteAccessIcon = 'industry-windows', | ||
| permissionListCardHeaderTitle = 'Permissions', | ||
| usernameInput = $bindable(undefined), | ||
| userAccount = $bindable(), | ||
| canToggleActive = true, | ||
| canEditAccountInfo = true, | ||
| doSendPasswordRecoveryToken = $bindable(false), | ||
| hasPermissionToChangePassword = false, | ||
| passwordValidationRules = undefined, | ||
| error = undefined, | ||
| success = undefined, | ||
| deactivateUser = undefined, | ||
| confirmPasswordSet = undefined, | ||
| accountInfoChanged = undefined, | ||
| sendPasswordRecoveryToken = undefined, | ||
| generateNewActivationPIN = () => Promise.resolve(), | ||
| userSites, | ||
| canEditSiteAccess = true, | ||
| siteAccessChange = undefined, | ||
| groupMembership, | ||
| canEditGroupMembership = true, | ||
| groupMembershipChange = undefined, | ||
| permissions, | ||
| permissionValueMap = new Map(), | ||
| groupPermissionValueMap = undefined, | ||
| permissionValueChange = undefined, | ||
| userAccountInfo, | ||
| siteAccess, | ||
| userGroupMembership, | ||
| permissionList, | ||
| }: Props = $props() | ||
| let cardHeight: number | undefined = $state() | ||
| </script> | ||
| <div class="form-row"> | ||
| <div class="row"> | ||
| <div class="col-12 col-lg-4 mb-2"> | ||
@@ -58,3 +133,3 @@ <UserAccountInfo | ||
| > | ||
| <slot name="userAccountInfo"></slot> | ||
| {@render userAccountInfo?.()} | ||
| </UserAccountInfo> | ||
@@ -71,3 +146,3 @@ </div> | ||
| > | ||
| <slot name="siteAccess"></slot> | ||
| {@render siteAccess?.()} | ||
| </UserSiteAccess> | ||
@@ -83,7 +158,7 @@ </div> | ||
| > | ||
| <slot name="userGroupMembership"></slot> | ||
| {@render userGroupMembership?.()} | ||
| </UserGroupMembership> | ||
| </div> | ||
| </div> | ||
| <div class="form-row"> | ||
| <div class="row"> | ||
| <div class="col-12"> | ||
@@ -99,5 +174,5 @@ <PermissionList | ||
| > | ||
| <slot name="permissionList"></slot> | ||
| {@render permissionList?.()} | ||
| </PermissionList> | ||
| </div> | ||
| </div> |
@@ -1,53 +0,40 @@ | ||
| import { SvelteComponent } from "svelte"; | ||
| import type { ErrorFn, IconName, UserSites, SuccessFn, SiteLabel, UserAccount, Permissions, GroupMembership, CanToggleActive, DeactivateUserFn, CanEditSiteAccess, SiteAccessChangeFn, PermissionValueMap, CanEditAccountInfo, ConfirmPasswordSetFn, AccountInfoChangedFn, CanEditGroupMembership, PasswordValidationRules, GroupMembershipChangeFn, PermissionValueChangeFn, GroupPermissionValueMap, GenerateNewActivationPINFn, DoSendPasswordRecoveryToken, SendPasswordRecoveryTokenFn, HasPermissionToChangePassword, PermissionListCardHeaderTitle } from './util'; | ||
| declare const __propDef: { | ||
| props: { | ||
| siteLabel?: SiteLabel; | ||
| userAccountInfoIcon?: IconName; | ||
| groupMembershipIcon?: IconName; | ||
| permissionListIcon?: IconName; | ||
| userSiteAccessIcon?: IconName; | ||
| permissionListCardHeaderTitle?: PermissionListCardHeaderTitle; | ||
| usernameInput?: HTMLInputElement | undefined; | ||
| userAccount: UserAccount; | ||
| canToggleActive?: CanToggleActive; | ||
| canEditAccountInfo?: CanEditAccountInfo; | ||
| doSendPasswordRecoveryToken?: DoSendPasswordRecoveryToken; | ||
| hasPermissionToChangePassword?: HasPermissionToChangePassword; | ||
| passwordValidationRules?: PasswordValidationRules | undefined; | ||
| error?: ErrorFn; | ||
| success?: SuccessFn; | ||
| deactivateUser?: DeactivateUserFn; | ||
| confirmPasswordSet?: ConfirmPasswordSetFn | undefined; | ||
| accountInfoChanged?: AccountInfoChangedFn; | ||
| sendPasswordRecoveryToken?: SendPasswordRecoveryTokenFn; | ||
| generateNewActivationPIN?: GenerateNewActivationPINFn; | ||
| userSites: UserSites; | ||
| canEditSiteAccess?: CanEditSiteAccess; | ||
| siteAccessChange?: SiteAccessChangeFn; | ||
| groupMembership: GroupMembership; | ||
| canEditGroupMembership?: CanEditGroupMembership; | ||
| groupMembershipChange?: GroupMembershipChangeFn; | ||
| permissions: Permissions; | ||
| permissionValueMap?: PermissionValueMap; | ||
| groupPermissionValueMap?: GroupPermissionValueMap; | ||
| permissionValueChange?: PermissionValueChangeFn; | ||
| }; | ||
| events: { | ||
| [evt: string]: CustomEvent<any>; | ||
| }; | ||
| slots: { | ||
| userAccountInfo: {}; | ||
| siteAccess: {}; | ||
| userGroupMembership: {}; | ||
| permissionList: {}; | ||
| }; | ||
| exports?: {} | undefined; | ||
| bindings?: string | undefined; | ||
| }; | ||
| export type UserConfigurationProps = typeof __propDef.props; | ||
| export type UserConfigurationEvents = typeof __propDef.events; | ||
| export type UserConfigurationSlots = typeof __propDef.slots; | ||
| export default class UserConfiguration extends SvelteComponent<UserConfigurationProps, UserConfigurationEvents, UserConfigurationSlots> { | ||
| interface Props { | ||
| siteLabel?: SiteLabel; | ||
| userAccountInfoIcon?: IconName; | ||
| groupMembershipIcon?: IconName; | ||
| permissionListIcon?: IconName; | ||
| userSiteAccessIcon?: IconName; | ||
| permissionListCardHeaderTitle?: PermissionListCardHeaderTitle; | ||
| usernameInput?: HTMLInputElement | undefined; | ||
| userAccount: UserAccount; | ||
| canToggleActive?: CanToggleActive; | ||
| canEditAccountInfo?: CanEditAccountInfo; | ||
| doSendPasswordRecoveryToken?: DoSendPasswordRecoveryToken; | ||
| hasPermissionToChangePassword?: HasPermissionToChangePassword; | ||
| passwordValidationRules?: PasswordValidationRules | undefined; | ||
| error?: ErrorFn; | ||
| success?: SuccessFn; | ||
| deactivateUser?: DeactivateUserFn; | ||
| confirmPasswordSet?: ConfirmPasswordSetFn | undefined; | ||
| accountInfoChanged?: AccountInfoChangedFn; | ||
| sendPasswordRecoveryToken?: SendPasswordRecoveryTokenFn; | ||
| generateNewActivationPIN?: GenerateNewActivationPINFn; | ||
| userSites: UserSites; | ||
| canEditSiteAccess?: CanEditSiteAccess; | ||
| siteAccessChange?: SiteAccessChangeFn; | ||
| groupMembership: GroupMembership; | ||
| canEditGroupMembership?: CanEditGroupMembership; | ||
| groupMembershipChange?: GroupMembershipChangeFn; | ||
| permissions: Permissions; | ||
| permissionValueMap?: PermissionValueMap; | ||
| groupPermissionValueMap?: GroupPermissionValueMap; | ||
| permissionValueChange?: PermissionValueChangeFn; | ||
| userAccountInfo?: import('svelte').Snippet; | ||
| siteAccess?: import('svelte').Snippet; | ||
| userGroupMembership?: import('svelte').Snippet; | ||
| permissionList?: import('svelte').Snippet; | ||
| } | ||
| export {}; | ||
| declare const UserConfiguration: import("svelte").Component<Props, {}, "doSendPasswordRecoveryToken" | "userAccount" | "usernameInput">; | ||
| type UserConfiguration = ReturnType<typeof UserConfiguration>; | ||
| export default UserConfiguration; |
@@ -1,10 +0,34 @@ | ||
| <script>import { getContext } from "svelte"; | ||
| import Icon from "@isoftdata/svelte-icon"; | ||
| import Checkbox from "@isoftdata/svelte-checkbox"; | ||
| import { translate as defaultTranslate } from "@isoftdata/utility-string"; | ||
| const { t: translate } = getContext("i18next") || { t: defaultTranslate }; | ||
| export let groupMembership; | ||
| export let canEditGroupMembership = true; | ||
| export let groupMembershipChange = void 0; | ||
| export let icon = "users"; | ||
| <script lang="ts"> | ||
| import type { i18n } from 'i18next' | ||
| import type { IconName, HTMLDivAttributes } from './' | ||
| import { getContext } from 'svelte' | ||
| import Icon from '@isoftdata/svelte-icon' | ||
| import Checkbox from '@isoftdata/svelte-checkbox' | ||
| import { translate as defaultTranslate } from '@isoftdata/utility-string' | ||
| const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate } | ||
| interface GroupMembership { | ||
| id: number | ||
| name: string | ||
| isMember: boolean | ||
| } | ||
| interface Props extends HTMLDivAttributes { | ||
| groupMembership: Array<GroupMembership> | ||
| canEditGroupMembership?: boolean | ||
| groupMembershipChange?: ((groupMembership: GroupMembership) => void | Promise<void>) | undefined | ||
| icon?: IconName | ||
| children?: import('svelte').Snippet | ||
| } | ||
| let { | ||
| groupMembership, | ||
| canEditGroupMembership = true, | ||
| groupMembershipChange = undefined, | ||
| icon = 'users', | ||
| children, | ||
| ...rest | ||
| }: Props = $props() | ||
| </script> | ||
@@ -14,3 +38,3 @@ | ||
| class="card" | ||
| {...$$restProps} | ||
| {...rest} | ||
| > | ||
@@ -27,9 +51,9 @@ <div class="card-header"> | ||
| > | ||
| {#each groupMembership as { id, name, isMember } (id)} | ||
| {#each groupMembership as { id, name, isMember }, index (id)} | ||
| <li class="list-group-item"> | ||
| <Checkbox | ||
| label={name} | ||
| bind:checked={isMember} | ||
| bind:checked={groupMembership[index].isMember} | ||
| disabled={!canEditGroupMembership} | ||
| on:change={async () => { | ||
| onchange={async () => { | ||
| await groupMembershipChange?.({ id, name, isMember }) | ||
@@ -41,3 +65,3 @@ }} | ||
| </ul> | ||
| <slot></slot> | ||
| {@render children?.()} | ||
| </div> |
@@ -1,32 +0,16 @@ | ||
| import { SvelteComponent } from "svelte"; | ||
| import type { IconName, HTMLDivAttributes } from './'; | ||
| declare const __propDef: { | ||
| props: HTMLDivAttributes & { | ||
| groupMembership: Array<{ | ||
| id: number; | ||
| name: string; | ||
| isMember: boolean; | ||
| }>; | ||
| canEditGroupMembership?: boolean; | ||
| groupMembershipChange?: ((groupMembership: { | ||
| id: number; | ||
| name: string; | ||
| isMember: boolean; | ||
| }) => void | Promise<void>) | undefined; | ||
| icon?: IconName; | ||
| }; | ||
| events: { | ||
| [evt: string]: CustomEvent<any>; | ||
| }; | ||
| slots: { | ||
| default: {}; | ||
| }; | ||
| exports?: {} | undefined; | ||
| bindings?: string | undefined; | ||
| }; | ||
| export type UserGroupMembershipProps = typeof __propDef.props; | ||
| export type UserGroupMembershipEvents = typeof __propDef.events; | ||
| export type UserGroupMembershipSlots = typeof __propDef.slots; | ||
| export default class UserGroupMembership extends SvelteComponent<UserGroupMembershipProps, UserGroupMembershipEvents, UserGroupMembershipSlots> { | ||
| interface GroupMembership { | ||
| id: number; | ||
| name: string; | ||
| isMember: boolean; | ||
| } | ||
| export {}; | ||
| interface Props extends HTMLDivAttributes { | ||
| groupMembership: Array<GroupMembership>; | ||
| canEditGroupMembership?: boolean; | ||
| groupMembershipChange?: ((groupMembership: GroupMembership) => void | Promise<void>) | undefined; | ||
| icon?: IconName; | ||
| children?: import('svelte').Snippet; | ||
| } | ||
| declare const UserGroupMembership: import("svelte").Component<Props, {}, "">; | ||
| type UserGroupMembership = ReturnType<typeof UserGroupMembership>; | ||
| export default UserGroupMembership; |
@@ -1,11 +0,38 @@ | ||
| <script>import { getContext } from "svelte"; | ||
| import Icon from "@isoftdata/svelte-icon"; | ||
| import Checkbox from "@isoftdata/svelte-checkbox"; | ||
| import { translate as defaultTranslate } from "@isoftdata/utility-string"; | ||
| const { t: translate } = getContext("i18next") || { t: defaultTranslate }; | ||
| export let userSites; | ||
| export let siteLabel = "Site"; | ||
| export let canEditSiteAccess = true; | ||
| export let siteAccessChange = void 0; | ||
| export let icon = "industry-windows"; | ||
| <script lang="ts"> | ||
| import type { Snippet } from 'svelte' | ||
| import type { i18n } from 'i18next' | ||
| import type { IconName, HTMLDivAttributes, SiteLabel } from './' | ||
| import { getContext } from 'svelte' | ||
| import Icon from '@isoftdata/svelte-icon' | ||
| import Checkbox from '@isoftdata/svelte-checkbox' | ||
| import { translate as defaultTranslate } from '@isoftdata/utility-string' | ||
| const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate } | ||
| interface SiteAccess { | ||
| id: number | ||
| code: string | ||
| name: string | ||
| isAuthorized: boolean | ||
| } | ||
| interface Props extends HTMLDivAttributes { | ||
| userSites: Array<SiteAccess> | ||
| siteLabel?: SiteLabel | ||
| canEditSiteAccess?: boolean | ||
| siteAccessChange?: ((siteAccess: SiteAccess) => void | Promise<void>) | undefined | ||
| icon?: IconName | ||
| children?: Snippet | ||
| } | ||
| let { | ||
| userSites = $bindable(), | ||
| siteLabel = 'Site', | ||
| canEditSiteAccess = true, | ||
| siteAccessChange = undefined, | ||
| icon = 'industry-windows', | ||
| children, | ||
| ...rest | ||
| }: Props = $props() | ||
| </script> | ||
@@ -15,3 +42,3 @@ | ||
| class="card" | ||
| {...$$restProps} | ||
| {...rest} | ||
| > | ||
@@ -31,3 +58,3 @@ <div class="card-header"> | ||
| disabled={!canEditSiteAccess} | ||
| on:change={async () => { | ||
| onchange={async () => { | ||
| await siteAccessChange?.(userSites[i]) | ||
@@ -39,3 +66,3 @@ }} | ||
| </ul> | ||
| <slot></slot> | ||
| {@render children?.()} | ||
| </div> |
@@ -1,35 +0,19 @@ | ||
| import { SvelteComponent } from "svelte"; | ||
| import type { Snippet } from 'svelte'; | ||
| import type { IconName, HTMLDivAttributes, SiteLabel } from './'; | ||
| declare const __propDef: { | ||
| props: HTMLDivAttributes & { | ||
| userSites: Array<{ | ||
| id: number; | ||
| code: string; | ||
| name: string; | ||
| isAuthorized: boolean; | ||
| }>; | ||
| siteLabel?: SiteLabel; | ||
| canEditSiteAccess?: boolean; | ||
| siteAccessChange?: ((siteAccess: { | ||
| id: number; | ||
| code: string; | ||
| name: string; | ||
| isAuthorized: boolean; | ||
| }) => void | Promise<void>) | undefined; | ||
| icon?: IconName; | ||
| }; | ||
| events: { | ||
| [evt: string]: CustomEvent<any>; | ||
| }; | ||
| slots: { | ||
| default: {}; | ||
| }; | ||
| exports?: {} | undefined; | ||
| bindings?: string | undefined; | ||
| }; | ||
| export type UserSiteAccessProps = typeof __propDef.props; | ||
| export type UserSiteAccessEvents = typeof __propDef.events; | ||
| export type UserSiteAccessSlots = typeof __propDef.slots; | ||
| export default class UserSiteAccess extends SvelteComponent<UserSiteAccessProps, UserSiteAccessEvents, UserSiteAccessSlots> { | ||
| interface SiteAccess { | ||
| id: number; | ||
| code: string; | ||
| name: string; | ||
| isAuthorized: boolean; | ||
| } | ||
| export {}; | ||
| interface Props extends HTMLDivAttributes { | ||
| userSites: Array<SiteAccess>; | ||
| siteLabel?: SiteLabel; | ||
| canEditSiteAccess?: boolean; | ||
| siteAccessChange?: ((siteAccess: SiteAccess) => void | Promise<void>) | undefined; | ||
| icon?: IconName; | ||
| children?: Snippet; | ||
| } | ||
| declare const UserSiteAccess: import("svelte").Component<Props, {}, "userSites">; | ||
| type UserSiteAccess = ReturnType<typeof UserSiteAccess>; | ||
| export default UserSiteAccess; |
+22
-22
@@ -33,24 +33,24 @@ import type { ComponentProps } from 'svelte'; | ||
| export declare const valueRank: Record<PermissionValue, number>; | ||
| export type GenerateNewActivationPINFn = ComponentProps<UserAccountInfo>['generateNewActivationPIN']; | ||
| export type SuccessFn = ComponentProps<UserAccountInfo>['success']; | ||
| export type ErrorFn = ComponentProps<UserAccountInfo>['error']; | ||
| export type AccountInfoChangedFn = ComponentProps<UserAccountInfo>['accountInfoChanged']; | ||
| export type CanEditAccountInfo = ComponentProps<UserAccountInfo>['canEditAccountInfo']; | ||
| export type CanToggleActive = ComponentProps<UserAccountInfo>['canToggleActive']; | ||
| export type HasPermissionToChangePassword = ComponentProps<UserAccountInfo>['hasPermissionToChangePassword']; | ||
| export type ConfirmPasswordSetFn = ComponentProps<PasswordSetModal>['confirmPasswordSet']; | ||
| export type DeactivateUserFn = ComponentProps<DeactivateUserModal>['deactivateUser']; | ||
| export type DoSendPasswordRecoveryToken = ComponentProps<PasswordRecoveryModal>['doSendPasswordRecoveryToken']; | ||
| export type SendPasswordRecoveryTokenFn = ComponentProps<PasswordRecoveryModal>['sendPasswordRecoveryToken']; | ||
| export type UserSites = ComponentProps<UserSiteAccess>['userSites']; | ||
| export type CanEditSiteAccess = ComponentProps<UserSiteAccess>['canEditSiteAccess']; | ||
| export type SiteAccessChangeFn = ComponentProps<UserSiteAccess>['siteAccessChange']; | ||
| export type GroupMembership = ComponentProps<UserGroupMembership>['groupMembership']; | ||
| export type GroupMembershipChangeFn = ComponentProps<UserGroupMembership>['groupMembershipChange']; | ||
| export type CanEditGroupMembership = ComponentProps<UserGroupMembership>['canEditGroupMembership']; | ||
| export type Permissions = ComponentProps<PermissionList>['permissions']; | ||
| export type PermissionValueMap = ComponentProps<PermissionList>['permissionValueMap']; | ||
| export type GroupPermissionValueMap = ComponentProps<PermissionList>['groupPermissionValueMap']; | ||
| export type PermissionValueChangeFn = ComponentProps<PermissionList>['permissionValueChange']; | ||
| export type PermissionListCardHeaderTitle = ComponentProps<PermissionList>['cardHeaderTitle']; | ||
| export type GenerateNewActivationPINFn = ComponentProps<typeof UserAccountInfo>['generateNewActivationPIN']; | ||
| export type SuccessFn = ComponentProps<typeof UserAccountInfo>['success']; | ||
| export type ErrorFn = ComponentProps<typeof UserAccountInfo>['error']; | ||
| export type AccountInfoChangedFn = ComponentProps<typeof UserAccountInfo>['accountInfoChanged']; | ||
| export type CanEditAccountInfo = ComponentProps<typeof UserAccountInfo>['canEditAccountInfo']; | ||
| export type CanToggleActive = ComponentProps<typeof UserAccountInfo>['canToggleActive']; | ||
| export type HasPermissionToChangePassword = ComponentProps<typeof UserAccountInfo>['hasPermissionToChangePassword']; | ||
| export type ConfirmPasswordSetFn = ComponentProps<typeof PasswordSetModal>['confirmPasswordSet']; | ||
| export type DeactivateUserFn = ComponentProps<typeof DeactivateUserModal>['deactivateUser']; | ||
| export type DoSendPasswordRecoveryToken = ComponentProps<typeof PasswordRecoveryModal>['doSendPasswordRecoveryToken']; | ||
| export type SendPasswordRecoveryTokenFn = ComponentProps<typeof PasswordRecoveryModal>['sendPasswordRecoveryToken']; | ||
| export type UserSites = ComponentProps<typeof UserSiteAccess>['userSites']; | ||
| export type CanEditSiteAccess = ComponentProps<typeof UserSiteAccess>['canEditSiteAccess']; | ||
| export type SiteAccessChangeFn = ComponentProps<typeof UserSiteAccess>['siteAccessChange']; | ||
| export type GroupMembership = ComponentProps<typeof UserGroupMembership>['groupMembership']; | ||
| export type GroupMembershipChangeFn = ComponentProps<typeof UserGroupMembership>['groupMembershipChange']; | ||
| export type CanEditGroupMembership = ComponentProps<typeof UserGroupMembership>['canEditGroupMembership']; | ||
| export type Permissions = ComponentProps<typeof PermissionList>['permissions']; | ||
| export type PermissionValueMap = ComponentProps<typeof PermissionList>['permissionValueMap']; | ||
| export type GroupPermissionValueMap = ComponentProps<typeof PermissionList>['groupPermissionValueMap']; | ||
| export type PermissionValueChangeFn = ComponentProps<typeof PermissionList>['permissionValueChange']; | ||
| export type PermissionListCardHeaderTitle = ComponentProps<typeof PermissionList>['cardHeaderTitle']; | ||
| /** Returns a Map where the key is the `permissioniId` and the value is the highest permission level for the given group membership | ||
@@ -57,0 +57,0 @@ * |
+74
-73
| { | ||
| "name": "@isoftdata/svelte-user-configuration", | ||
| "version": "1.3.3", | ||
| "scripts": { | ||
| "dev": "vite dev", | ||
| "build": "vite build && npm run package", | ||
| "preview": "vite preview", | ||
| "package": "svelte-kit sync && svelte-package && publint", | ||
| "prepublishOnly": "npm run package", | ||
| "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", | ||
| "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", | ||
| "lint": "prettier --check . && eslint .", | ||
| "format": "prettier --write ." | ||
| }, | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "svelte": "./dist/index.js" | ||
| } | ||
| }, | ||
| "sideEffects": [ | ||
| "**/*.css" | ||
| ], | ||
| "files": [ | ||
| "dist", | ||
| "!dist/**/*.test.*", | ||
| "!dist/**/*.spec.*" | ||
| ], | ||
| "peerDependencies": { | ||
| "svelte": "^4.0.0 || ^5.16.3" | ||
| }, | ||
| "devDependencies": { | ||
| "@fortawesome/fontawesome-common-types": "^6.7.2", | ||
| "@isoftdata/prettier-config": "^1.0.3", | ||
| "@isoftdata/svelte-bootstrap-version-switcher": "^1.0.2", | ||
| "@sveltejs/adapter-auto": "^3.0.0", | ||
| "@sveltejs/kit": "^2.0.0", | ||
| "@sveltejs/package": "^2.0.0", | ||
| "@sveltejs/vite-plugin-svelte": "^3.0.0", | ||
| "@types/eslint": "^9.6.0", | ||
| "eslint": "^9.0.0", | ||
| "eslint-config-prettier": "^9.1.0", | ||
| "eslint-plugin-svelte": "^2.36.0", | ||
| "globals": "^15.0.0", | ||
| "i18next": "^24.2.1", | ||
| "prettier": "^3.1.1", | ||
| "prettier-plugin-svelte": "^3.1.2", | ||
| "publint": "^0.2.0", | ||
| "svelte": "^4.2.7", | ||
| "svelte-check": "^4.0.0", | ||
| "type-fest": "^4.33.0", | ||
| "typescript": "^5.0.0", | ||
| "typescript-eslint": "^8.0.0", | ||
| "vite": "^5.0.11" | ||
| }, | ||
| "svelte": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "type": "module", | ||
| "prettier": "@isoftdata/prettier-config", | ||
| "dependencies": { | ||
| "@isoftdata/browser-event": "^2.3.2", | ||
| "@isoftdata/svelte-button": "^1.4.1", | ||
| "@isoftdata/svelte-checkbox": "^1.5.1", | ||
| "@isoftdata/svelte-icon": "^1.4.0", | ||
| "@isoftdata/svelte-input": "^1.6.0", | ||
| "@isoftdata/svelte-modal": "^1.3.1", | ||
| "@isoftdata/svelte-password-fields": "^1.4.1", | ||
| "@isoftdata/svelte-select": "^1.5.0", | ||
| "@isoftdata/svelte-table": "^1.16.0", | ||
| "@isoftdata/svelte-textarea": "^1.2.0", | ||
| "@isoftdata/utility-string": "^2.1.0", | ||
| "just-camel-case": "^6.2.0", | ||
| "klona": "^2.0.6" | ||
| } | ||
| "name": "@isoftdata/svelte-user-configuration", | ||
| "version": "2.0.0", | ||
| "scripts": { | ||
| "dev": "vite dev", | ||
| "build": "vite build && npm run package", | ||
| "preview": "vite preview", | ||
| "package": "svelte-kit sync && svelte-package && publint", | ||
| "prepublishOnly": "npm run package", | ||
| "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", | ||
| "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", | ||
| "lint": "prettier --check . && eslint .", | ||
| "format": "prettier --write ." | ||
| }, | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "svelte": "./dist/index.js" | ||
| } | ||
| }, | ||
| "sideEffects": [ | ||
| "**/*.css" | ||
| ], | ||
| "files": [ | ||
| "dist", | ||
| "!dist/**/*.test.*", | ||
| "!dist/**/*.spec.*" | ||
| ], | ||
| "peerDependencies": { | ||
| "svelte": "^5.23.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@fortawesome/fontawesome-common-types": "^6.7.2", | ||
| "@isoftdata/prettier-config": "^2.0.4", | ||
| "@isoftdata/svelte-bootstrap-version-switcher": "^1.1.0", | ||
| "@sveltejs/adapter-auto": "^6.0.0", | ||
| "@sveltejs/kit": "^2.5.27", | ||
| "@sveltejs/package": "^2.3.7", | ||
| "@sveltejs/vite-plugin-svelte": "^5.0.3", | ||
| "@types/eslint": "^9.6.0", | ||
| "eslint": "^9.0.0", | ||
| "eslint-config-prettier": "^9.1.0", | ||
| "eslint-plugin-svelte": "^2.45.1", | ||
| "globals": "^15.0.0", | ||
| "i18next": "^24.2.1", | ||
| "prettier": "^3.1.1", | ||
| "prettier-plugin-svelte": "^3.2.6", | ||
| "publint": "^0.2.0", | ||
| "svelte": "^5.0.0", | ||
| "svelte-check": "^4.0.0", | ||
| "type-fest": "^4.33.0", | ||
| "typescript": "^5.5.0", | ||
| "typescript-eslint": "^8.0.0", | ||
| "vite": "^6.2.5" | ||
| }, | ||
| "svelte": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "type": "module", | ||
| "prettier": "@isoftdata/prettier-config", | ||
| "dependencies": { | ||
| "@isoftdata/browser-event": "^2.3.2", | ||
| "@isoftdata/svelte-button": "^2.1.0", | ||
| "@isoftdata/svelte-checkbox": "^2.0.1", | ||
| "@isoftdata/svelte-icon": "^2.0.0", | ||
| "@isoftdata/svelte-input": "^2.0.1", | ||
| "@isoftdata/svelte-modal": "^2.0.4", | ||
| "@isoftdata/svelte-password-fields": "^1.5.0", | ||
| "@isoftdata/svelte-select": "^2.0.1", | ||
| "@isoftdata/svelte-table": "^2.1.1", | ||
| "@isoftdata/svelte-textarea": "^2.0.0", | ||
| "@isoftdata/utility-string": "^2.1.0", | ||
| "just-camel-case": "^6.2.0", | ||
| "klona": "^2.0.6", | ||
| "svelte": "^5.23.2" | ||
| } | ||
| } |
+9
-2
@@ -11,2 +11,9 @@ # Svelte UserConfiguration | ||
| ## Breaking changes | ||
| ### 2.0.0 | ||
| - Require Svelte 5 | ||
| - Slots -> Snippets | ||
| - Events -> Callbacks | ||
| ## Props | ||
@@ -46,3 +53,3 @@ | ||
| | `permissions` | `Permissions` | ✅ | - | The list of permissions for the user. | | ||
| | `permissionValueMap` | `PermissionValueMap` | ✅ | `new Map()` | A mapping of permissions to their values. | | ||
| | `permissionValueMap` | `PermissionValueMap` | ✅ | `new SvelteMap()` | A mapping of permissions to their values. **Note this *must* be a `SvelteMap`** or reactivity will not work. | | ||
| | `groupPermissionValueMap` | `GroupPermissionValueMap` | ❌ | `undefined` | A mapping of group-based permissions. | | ||
@@ -150,5 +157,5 @@ | ||
| } | ||
| permissionValueMap = new Map(permissionValueMap) | ||
| permissionValueMap = new SvelteMap(permissionValueMap) | ||
| }} | ||
| /> | ||
| ``` |
64260
9.08%159
4.61%15
7.14%276
-27.75%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed