435 lines
14 KiB
Vue
435 lines
14 KiB
Vue
<template>
|
|
<div
|
|
id="modal_account_edit_or_create_new"
|
|
class="w-[640px] bg-[#FFFFFF] rounded shadow-lg flex flex-col"
|
|
>
|
|
<ModalHeader :headerText="modalTitle" />
|
|
|
|
<main class="flex row min-h-[96px] my-[32px] flex-col px-6">
|
|
<div
|
|
class="input-row w-full flex py-2 h-[40px] mb-4 items-center justify-between"
|
|
>
|
|
<div
|
|
class="field-label text-sm flex items-center font-medium justify-end"
|
|
>
|
|
<span class="align-right-span flex w-[122px] justify-end">
|
|
{{ i18next.t("AcctMgmt.Account") }}
|
|
</span>
|
|
</div>
|
|
<div class="input-and-error flex flex-col">
|
|
<input
|
|
id="input_account_field"
|
|
class="w-[454px] rounded p-1 border border-[1px] border-[#64748B] flex items-center h-[40px] outline-none"
|
|
v-model="inputUserAccount"
|
|
:class="{
|
|
'text-[#000000]': isAccountUnique,
|
|
'text-[#FF3366]': !isAccountUnique,
|
|
'border-[#FF3366]': !isAccountUnique,
|
|
}"
|
|
:readonly="!isEditable"
|
|
autocomplete="off"
|
|
@dblclick="onInputDoubleClick"
|
|
/>
|
|
<div v-show="!isAccountUnique" class="error-wrapper my-2">
|
|
<div class="error-msg-section flex justify-start">
|
|
<img
|
|
src="@/assets/icon-alert.svg"
|
|
alt="!"
|
|
class="exclamation-img flex mr-2"
|
|
/>
|
|
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
|
|
{{ i18next.t("AcctMgmt.AccountNotUnique") }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="input-row w-full flex py-2 h-[40px] mb-4 items-center justify-between"
|
|
>
|
|
<div
|
|
class="field-label text-sm flex items-center font-medium justify-end"
|
|
>
|
|
<span class="align-right-span flex w-[122px] justify-end">
|
|
{{ i18next.t("AcctMgmt.FullName") }}
|
|
</span>
|
|
</div>
|
|
<input
|
|
id="input_name_field"
|
|
class="w-[454px] rounded p-1 border border-[1px] border-[#64748B] flex items-center h-[40px] outline-none"
|
|
v-model="inputName"
|
|
:readonly="!isEditable"
|
|
@dblclick="onInputDoubleClick"
|
|
@focus="onInputNameFocus"
|
|
autocomplete="off"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-show="!isSSO"
|
|
class="input-row w-full flex py-2 mb-4 items-center justify-between"
|
|
>
|
|
<div
|
|
class="field-label text-sm flex flex-col items-start font-medium justify-end"
|
|
:class="{
|
|
'h-[100px]': whichCurrentModal !== MODAL_CREATE_NEW,
|
|
}"
|
|
>
|
|
<!-- Layout of Password field changes depending on whether this is the create or edit modal -->
|
|
<span
|
|
class="align-right-span flex h-full w-[122px] justify-end"
|
|
:class="{
|
|
'pt-[12px]': whichCurrentModal !== MODAL_CREATE_NEW,
|
|
'text-top': whichCurrentModal !== MODAL_CREATE_NEW,
|
|
'h-[40px]': whichCurrentModal === MODAL_CREATE_NEW,
|
|
'items-center': whichCurrentModal === MODAL_CREATE_NEW,
|
|
}"
|
|
>
|
|
{{ i18next.t("AcctMgmt.Password") }}
|
|
</span>
|
|
<span
|
|
v-if="whichCurrentModal === MODAL_CREATE_NEW"
|
|
class="dummy-cell flex w-[122px] h-[40px]"
|
|
>
|
|
</span>
|
|
</div>
|
|
<div
|
|
class="reset-btn-and-input-pwd-and-error flex flex-col"
|
|
:class="{ 'h-[100px]': whichCurrentModal !== MODAL_CREATE_NEW }"
|
|
>
|
|
<div
|
|
v-if="whichCurrentModal !== MODAL_CREATE_NEW"
|
|
class="flex account-edit-section justify-start w-[454px]"
|
|
>
|
|
<button
|
|
class="flex w-[85px] h-[40px] reset-btn rounded-full border-[1px] border-[#666666] cursor-pointer items-center justify-center hover:text-[#0099FF] hover:border-[#0099FF] mb-[20px]"
|
|
@click="onResetPwdButtonClick"
|
|
>
|
|
{{ i18next.t("AcctMgmt.Reset") }}
|
|
</button>
|
|
</div>
|
|
<div
|
|
v-if="
|
|
whichCurrentModal === MODAL_CREATE_NEW || isResetPwdSectionShow
|
|
"
|
|
class="w-[454px] flex flex-col input-and-error-msg"
|
|
>
|
|
<div
|
|
class="input-and-eye flex items-center w-full h-[40px] relative border-[1px] rounded"
|
|
:class="{
|
|
'border-[#FF3366]': !isPwdLengthValid,
|
|
'border-[#64748B]': isPwdLengthValid,
|
|
}"
|
|
>
|
|
<input
|
|
id="input_first_pwd"
|
|
class="outline-none p-1 w-[352px]"
|
|
:type="isPwdEyeOn ? 'text' : 'password'"
|
|
v-model="inputPwd"
|
|
:readonly="!isEditable"
|
|
@dblclick="onInputDoubleClick"
|
|
autocomplete="off"
|
|
:class="{
|
|
'color-[#FF3366]': !isPwdLengthValid,
|
|
'color-[#64748B]': isPwdLengthValid,
|
|
}"
|
|
/>
|
|
<img
|
|
id="eye_button"
|
|
v-if="isPwdEyeOn"
|
|
src="@/assets/icon-eye-open.svg"
|
|
class="absolute right-[8px] cursor-pointer"
|
|
@mousedown="togglePwdEyeBtn(true)"
|
|
@mouseup="togglePwdEyeBtn(false)"
|
|
alt=""
|
|
/>
|
|
<img
|
|
v-else
|
|
src="@/assets/icon-eye-hide.svg"
|
|
class="absolute right-[8px] cursor-pointer"
|
|
@mousedown="togglePwdEyeBtn(true)"
|
|
@mouseup="togglePwdEyeBtn(false)"
|
|
alt="eye"
|
|
/>
|
|
</div>
|
|
<div class="error-msg-section flex justify-start mt-4">
|
|
<img
|
|
v-show="!isPwdLengthValid"
|
|
src="@/assets/icon-alert.svg"
|
|
alt="!"
|
|
class="exclamation-img flex mr-2"
|
|
/>
|
|
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
|
|
{{
|
|
isPwdLengthValid
|
|
? ""
|
|
: i18next.t("AcctMgmt.PwdLengthNotEnough")
|
|
}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="whichCurrentModal === MODAL_CREATE_NEW"
|
|
class="checkbox-row w-full grid-cols-[122px_1fr] gap-x-4 flex py-2 h-[40px] my-4 items-center"
|
|
>
|
|
<div class="dummy field-label flex items-center w-[122px]"></div>
|
|
<!-- A dummy field is also used here to maintain spacing -->
|
|
<section id="account_create_checkboxes_section" class="flex flex-col">
|
|
<div class="checkbox-and-text flex">
|
|
<IconChecked
|
|
:isChecked="isSetAsAdminChecked"
|
|
@click="toggleIsAdmin"
|
|
/>
|
|
<span class="flex checkbox-text">
|
|
{{ i18next.t("AcctMgmt.SetAsAdmin") }}
|
|
</span>
|
|
</div>
|
|
<div class="checkbox-and-text flex">
|
|
<IconChecked
|
|
:isChecked="isSetActivedChecked"
|
|
@click="toggleIsActivated"
|
|
/>
|
|
<span class="flex checkbox-text">
|
|
{{ i18next.t("AcctMgmt.ActivateNow") }}
|
|
</span>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
<footer class="flex row footer justify-end pr-[32px] pb-8">
|
|
<button
|
|
class="cancel-btn rounded-full w-[92px] h-10 px-2.5 py-6 border border-[1px] border-[#64748B] flex justify-center items-center text-[#4E5969] font-medium hover:bg-[#64748B] hover:border-[#CBD5E1] hover:text-[#FFFFFF]"
|
|
@click="onCancelBtnClick"
|
|
>
|
|
{{ i18next.t("Global.Cancel") }}
|
|
</button>
|
|
<button
|
|
class="confirm-btn rounded-full w-[92px] h-10 px-2.5 py-6 flex justify-center items-center text-[#ffffff] font-medium ml-[16px]"
|
|
@click="onConfirmBtnClick"
|
|
:disabled="isConfirmDisabled"
|
|
:class="{
|
|
'bg-[#0099FF]': !isConfirmDisabled,
|
|
'bg-[#E2E8F0]': isConfirmDisabled,
|
|
}"
|
|
>
|
|
{{ i18next.t("Global.Confirm") }}
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
// The Lucia project.
|
|
// Copyright 2024-2026 DSP, inc. All rights reserved.
|
|
// Authors:
|
|
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
|
|
/**
|
|
* @module views/AccountManagement/ModalAccountEditCreate Modal
|
|
* for creating new accounts or editing existing accounts with
|
|
* username, name, password fields and validation.
|
|
*/
|
|
|
|
import { computed, ref, watch } from "vue";
|
|
import i18next from "@/i18n/i18n.js";
|
|
import { useModalStore } from "@/stores/modal";
|
|
import { useRouter } from "vue-router";
|
|
import { useToast } from "vue-toast-notification";
|
|
import { useAcctMgmtStore } from "@/stores/acctMgmt";
|
|
import ModalHeader from "./ModalHeader.vue";
|
|
import IconChecked from "@/components/icons/IconChecked.vue";
|
|
import {
|
|
MODAL_CREATE_NEW,
|
|
MODAL_ACCT_EDIT,
|
|
PWD_VALID_LENGTH,
|
|
} from "@/constants/constants.js";
|
|
|
|
const acctMgmtStore = useAcctMgmtStore();
|
|
const modalStore = useModalStore();
|
|
|
|
const router = useRouter();
|
|
const toast = useToast();
|
|
|
|
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
|
|
const isPwdEyeOn = ref(false);
|
|
const isConfirmDisabled = ref(true);
|
|
const isPwdLengthValid = ref(true);
|
|
const isResetPwdSectionShow = ref(false);
|
|
|
|
const isSetAsAdminChecked = ref(false);
|
|
const isSetActivedChecked = ref(true);
|
|
|
|
const whichCurrentModal = computed(() => modalStore.whichModal);
|
|
|
|
const isSSO = computed(() => acctMgmtStore.currentViewingUser.is_sso);
|
|
const username = computed(() => acctMgmtStore.currentViewingUser.username);
|
|
const name = computed(() => acctMgmtStore.currentViewingUser.name);
|
|
|
|
const inputUserAccount = ref(
|
|
whichCurrentModal.value === MODAL_CREATE_NEW
|
|
? ""
|
|
: currentViewingUser.value.username,
|
|
);
|
|
const inputName = ref(
|
|
whichCurrentModal.value === MODAL_CREATE_NEW
|
|
? ""
|
|
: currentViewingUser.value.name,
|
|
);
|
|
const inputPwd = ref("");
|
|
const isAccountUnique = ref(true);
|
|
const isEditable = ref(true);
|
|
|
|
// Since adding this watch, filling in the password field no longer clears the account or full name fields.
|
|
watch(whichCurrentModal, (newVal) => {
|
|
if (newVal === MODAL_CREATE_NEW) {
|
|
inputUserAccount.value = "";
|
|
inputName.value = "";
|
|
} else {
|
|
inputUserAccount.value = currentViewingUser.value.username;
|
|
inputName.value = currentViewingUser.value.name;
|
|
}
|
|
});
|
|
|
|
const modalTitle = computed(() => {
|
|
return modalStore.whichModal === MODAL_CREATE_NEW
|
|
? i18next.t("AcctMgmt.CreateNew")
|
|
: i18next.t("AcctMgmt.AccountEdit");
|
|
});
|
|
|
|
const togglePwdEyeBtn = (toBeOpen) => {
|
|
isPwdEyeOn.value = toBeOpen;
|
|
};
|
|
|
|
const validatePwdLength = () => {
|
|
isPwdLengthValid.value =
|
|
!isResetPwdSectionShow.value || inputPwd.value.length >= PWD_VALID_LENGTH;
|
|
};
|
|
|
|
const onInputDoubleClick = () => {
|
|
// Enable edit mode
|
|
isEditable.value = true;
|
|
};
|
|
|
|
const onConfirmBtnClick = async () => {
|
|
// rule for minimum length
|
|
validatePwdLength();
|
|
if (!isPwdLengthValid.value) {
|
|
return;
|
|
}
|
|
|
|
// rule for account uniqueness
|
|
switch (whichCurrentModal.value) {
|
|
case MODAL_CREATE_NEW:
|
|
await checkAccountIsUnique();
|
|
if (!isAccountUnique.value) {
|
|
return;
|
|
}
|
|
await acctMgmtStore.createNewAccount({
|
|
username: inputUserAccount.value,
|
|
password: inputPwd.value === undefined ? "" : inputPwd.value,
|
|
name: inputName.value,
|
|
is_admin: isSetAsAdminChecked.value,
|
|
is_active: isSetActivedChecked.value,
|
|
});
|
|
toast.success(i18next.t("AcctMgmt.MsgAccountAdded"));
|
|
modalStore.closeModal();
|
|
acctMgmtStore.setShouldUpdateList(true);
|
|
await router.push("/account-admin");
|
|
break;
|
|
case MODAL_ACCT_EDIT:
|
|
await checkAccountIsUnique();
|
|
if (!isAccountUnique.value) {
|
|
return;
|
|
}
|
|
// Note that the old username and new username can be different
|
|
// Distinguish between cases with and without a password
|
|
if (isResetPwdSectionShow.value) {
|
|
await acctMgmtStore.editAccount(currentViewingUser.value.username, {
|
|
newUsername: inputUserAccount.value,
|
|
password: inputPwd.value,
|
|
name: inputName.value === undefined ? "" : inputName.value,
|
|
is_active: true,
|
|
});
|
|
} else {
|
|
await acctMgmtStore.editAccount(currentViewingUser.value.username, {
|
|
newUsername: inputUserAccount.value,
|
|
name: inputName.value === undefined ? "" : inputName.value,
|
|
is_active: true,
|
|
});
|
|
}
|
|
toast.success(i18next.t("AcctMgmt.MsgAccountEdited"));
|
|
isEditable.value = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
const checkAccountIsUnique = async () => {
|
|
// If the user has not modified the field, no backend API call is needed
|
|
if (inputUserAccount.value === username.value) {
|
|
return true;
|
|
}
|
|
const isAccountAlreadyExistAPISuccess = await acctMgmtStore.getUserDetail(
|
|
inputUserAccount.value,
|
|
);
|
|
isAccountUnique.value = !isAccountAlreadyExistAPISuccess;
|
|
return isAccountUnique.value;
|
|
};
|
|
|
|
const toggleIsAdmin = () => {
|
|
if (isEditable.value) {
|
|
isSetAsAdminChecked.value = !isSetAsAdminChecked.value;
|
|
}
|
|
};
|
|
|
|
const toggleIsActivated = () => {
|
|
if (isEditable.value) {
|
|
isSetActivedChecked.value = !isSetActivedChecked.value;
|
|
}
|
|
};
|
|
|
|
const onInputNameFocus = () => {
|
|
if (isConfirmDisabled.value) {
|
|
isConfirmDisabled.value = false;
|
|
}
|
|
};
|
|
|
|
const onResetPwdButtonClick = () => {
|
|
isResetPwdSectionShow.value = !isResetPwdSectionShow.value;
|
|
// Must clear the password input field
|
|
inputPwd.value = "";
|
|
};
|
|
|
|
watch(
|
|
[inputPwd, inputUserAccount, inputName],
|
|
([newPwd, newAccount, newName]) => {
|
|
// Enable the confirm button when all fields are non-empty
|
|
if (newAccount.length > 0 && newName.length > 0) {
|
|
isConfirmDisabled.value = false;
|
|
}
|
|
if (whichCurrentModal.value !== MODAL_CREATE_NEW) {
|
|
if (isResetPwdSectionShow.value && newPwd.length < PWD_VALID_LENGTH) {
|
|
isConfirmDisabled.value = true;
|
|
}
|
|
} else {
|
|
if (newPwd.length < PWD_VALID_LENGTH) {
|
|
isConfirmDisabled.value = true;
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
function onCancelBtnClick() {
|
|
modalStore.closeModal();
|
|
}
|
|
</script>
|
|
<style>
|
|
#modal_account_edit {
|
|
background-color: #ffffff;
|
|
backdrop-filter: opacity(
|
|
1
|
|
); /* Prevent child elements from inheriting parent's opacity */
|
|
}
|
|
</style>
|