WIP: validate confirm password and show error message

This commit is contained in:
Cindy Chang
2024-06-20 15:54:14 +08:00
parent 26441d3979
commit 05caf819bb
10 changed files with 191 additions and 20 deletions

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.78415 2.03878C9.62269 1.72766 9.36953 1.46508 9.05382 1.28129C8.73812 1.09751 8.37272 1 7.99974 1C7.62675 1 7.26135 1.09751 6.94565 1.28129C6.62994 1.46508 6.37678 1.72766 6.21532 2.03878L0.520353 12.4048C-0.160994 13.6423 0.699988 15.2857 2.30407 15.2857H13.6947C15.2995 15.2857 16.1591 13.643 15.4791 12.4048L9.78415 2.03878ZM7.99974 5.54628C8.18584 5.54628 8.36432 5.61468 8.49591 5.73645C8.6275 5.85822 8.70143 6.02337 8.70143 6.19557V9.44205C8.70143 9.61425 8.6275 9.77941 8.49591 9.90117C8.36432 10.0229 8.18584 10.0913 7.99974 10.0913C7.81363 10.0913 7.63516 10.0229 7.50356 9.90117C7.37197 9.77941 7.29804 9.61425 7.29804 9.44205V6.19557C7.29804 6.02337 7.37197 5.85822 7.50356 5.73645C7.63516 5.61468 7.81363 5.54628 7.99974 5.54628ZM7.99974 11.0653C8.18584 11.0653 8.36432 11.1337 8.49591 11.2555C8.6275 11.3772 8.70143 11.5424 8.70143 11.7146V12.0392C8.70143 12.2114 8.6275 12.3766 8.49591 12.4984C8.36432 12.6201 8.18584 12.6885 7.99974 12.6885C7.81363 12.6885 7.63516 12.6201 7.50356 12.4984C7.37197 12.3766 7.29804 12.2114 7.29804 12.0392V11.7146C7.29804 11.5424 7.37197 11.3772 7.50356 11.2555C7.63516 11.1337 7.81363 11.0653 7.99974 11.0653Z" fill="#FF3366"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 5.27L3.28 4L20 20.72L18.73 22L15.65 18.92C14.5 19.3 13.28 19.5 12 19.5C7 19.5 2.73 16.39 1 12C1.69 10.24 2.79 8.69 4.19 7.46L2 5.27ZM12 9C12.7956 9 13.5587 9.31607 14.1213 9.87868C14.6839 10.4413 15 11.2044 15 12C15.0005 12.3406 14.943 12.6787 14.83 13L11 9.17C11.3213 9.05698 11.6594 8.99949 12 9ZM12 4.5C17 4.5 21.27 7.61 23 12C22.1839 14.0732 20.7969 15.8727 19 17.19L17.58 15.76C18.9629 14.8034 20.0782 13.5091 20.82 12C20.0116 10.3499 18.7564 8.95977 17.1973 7.9875C15.6381 7.01524 13.8375 6.49988 12 6.5C10.91 6.5 9.84 6.68 8.84 7L7.3 5.47C8.74 4.85 10.33 4.5 12 4.5ZM3.18 12C3.98844 13.6501 5.24357 15.0402 6.80273 16.0125C8.36189 16.9848 10.1625 17.5001 12 17.5C12.69 17.5 13.37 17.43 14 17.29L11.72 15C11.0242 14.9254 10.3748 14.6149 9.87997 14.12C9.38512 13.6252 9.07458 12.9758 9 12.28L5.6 8.87C4.61 9.72 3.78 10.78 3.18 12Z" fill="#64748B"/>
</svg>

After

Width:  |  Height:  |  Size: 969 B

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 9C12.7956 9 13.5587 9.31607 14.1213 9.87868C14.6839 10.4413 15 11.2044 15 12C15 12.7956 14.6839 13.5587 14.1213 14.1213C13.5587 14.6839 12.7956 15 12 15C11.2044 15 10.4413 14.6839 9.87868 14.1213C9.31607 13.5587 9 12.7956 9 12C9 11.2044 9.31607 10.4413 9.87868 9.87868C10.4413 9.31607 11.2044 9 12 9ZM12 4.5C17 4.5 21.27 7.61 23 12C21.27 16.39 17 19.5 12 19.5C7 19.5 2.73 16.39 1 12C2.73 7.61 7 4.5 12 4.5ZM3.18 12C3.98825 13.6503 5.24331 15.0407 6.80248 16.0133C8.36165 16.9858 10.1624 17.5013 12 17.5013C13.8376 17.5013 15.6383 16.9858 17.1975 16.0133C18.7567 15.0407 20.0117 13.6503 20.82 12C20.0117 10.3497 18.7567 8.95925 17.1975 7.98675C15.6383 7.01424 13.8376 6.49868 12 6.49868C10.1624 6.49868 8.36165 7.01424 6.80248 7.98675C5.24331 8.95925 3.98825 10.3497 3.18 12Z" fill="#64748B"/>
</svg>

After

Width:  |  Height:  |  Size: 909 B

38
src/components/Badge.vue Normal file
View File

@@ -0,0 +1,38 @@
<template>
<div
class="status-badge rounded-full max-w-[95px] w-fit px-3 inline-flex items-center text-[#ffffff] text-[14px] mr-2"
:class="{
'badge-activated': isActivated,
'badge-deactivated': !isActivated,
'bg-[#0099FF]': isActivated,
'bg-[#C9CDD4]': !isActivated,
}"
>
{{ displayText }}
</div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
isActivated: {
type: Boolean,
required: true,
default: true,
},
displayText: {
type: String,
required: true,
default: "Status",
}
},
setup(props) {
return {
isActivated: props.isActivated,
displayText: props.displayText,
};
}
});
</script>

View File

@@ -16,16 +16,18 @@ export const accountList = [
account: "REDACTED-USER1",
fullName: "Alice Zheng",
adminRights: true,
accountActivation: true,
accountActivation: false,
detail: "abcde",
isSSO: true,
},
{
id: 345,
account: "REDACTED-USER2",
fullName: "Mike Chen",
adminRights: true,
adminRights: false,
accountActivation: true,
detail: "abcde",
isSSO: false,
},
{
id: 88,
@@ -34,6 +36,7 @@ export const accountList = [
adminRights: true,
accountActivation: true,
detail: "abcde",
isSSO: false,
},
];
export const knownScaleLineChartOptions = {

View File

@@ -21,7 +21,10 @@
"Detail": "Detail",
"Edit": "Edit",
"AccountEdit": "Account Edit",
"AccountInformation": "Account Information"
"AccountInformation": "Account Information",
"Password": "Password",
"ConfirmPassword": "Confirm Password",
"PwdNotMatch": "Confirm Password does not match."
},
"Compare": {
"timeUsage": "Time Usage",

View File

@@ -11,6 +11,7 @@ export default defineStore('acctMgmtStore', {
adminRights: true,
accountActivation: true,
detail: "abcde",
isSSO: true,
}
}),
getters: {

View File

@@ -151,6 +151,7 @@ export default {
},
onEditBtnClick(dataId){
this.openModal(MODAL_ACCT_EDIT);
this.setCurrentViewingUser(dataId);
},
...mapActions(useModalStore, ['openModal']),
...mapActions(useAcctMgmtStore, ['setCurrentViewingUser']),

View File

@@ -1,28 +1,95 @@
<template>
<div id="modal_account_edit" class="w-[600px] h-[296px] bg-[#FFFFFF] rounded
<div id="modal_account_edit" class="w-[640px] bg-[#FFFFFF] rounded
shadow-lg flex flex-col">
<ModalHeader :headerText="i18next.t('AcctMgmt.AccountEdit')"/>
<main class="flex row h-[96px] my-[32px] flex-col px-8">
<div class="input-row w-full flex py-3">
<span class="field-label w-[68px] text-sm flex items-center font-medium mr-4 justify-end">{{ i18next.t("AcctMgmt.Account") }} </span>
<input class="w-[454px] rounded p-1 btn btn-sm btn-neutralborder border-[1px] border-[#64748B] flex items-center h-[40px]"/>
<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 mr-4 justify-end">
<span class="align-right-span flex w-[100px] justify-end">
{{ i18next.t("AcctMgmt.Account") }}
</span>
</div>
<div class="input-row w-full flex py-3 items-center">
<span class="field-label w-[68px] text-sm flex items-center font-medium mr-4 justify-end">{{ i18next.t("AcctMgmt.FullName") }} </span>
<input class="w-[454px] rounded p-1 btn btn-sm btn-neutralborder border-[1px] border-[#64748B] flex items-center h-[40px]"/>
<input id="input_account_field"
class="w-[454px] rounded p-1 border border-[1px] border-[#64748B] flex items-center h-[40px] outline-none"
:value="account"/>
</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 mr-4 justify-end">
<span class="align-right-span flex w-[100px] justify-end">
{{ i18next.t("AcctMgmt.FullName") }}
</span>
</div>
<input class="w-[454px] rounded p-1 border border-[1px] border-[#64748B] flex items-center h-[40px] outline-none"
:value="fullName"/>
</div>
<div v-show="!isSSO" 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 mr-4 justify-end">
<span class="align-right-span flex w-[100px] justify-end">
{{ i18next.t("AcctMgmt.Password") }}
</span>
</div>
<div class="w-[454px] rounded border-[1px] border-[#64748B] flex items-center h-[40px] relative">
<input class="outline-none p-1 w-[352px]" :type="isPwdEyeOn ? 'text' : 'password'"
v-model="inputPwd"/>
<img v-if="isPwdEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer"
@click="togglePwdEyeBtn"/>
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer" @click="togglePwdEyeBtn"/>
</div>
</div>
<div v-show="!isSSO" id="confirm_pwd_row" class="input-row w-full grid grid-cols-2 grid-cols-[122px_1fr] gap-x-4
mb-4 flex items-center"> <!-- 2-by-2 的格子其中左下角是一個dummy格子其沒有內容 -->
<span class="field-label w-[100px] text-sm flex items-center justify-end text-right font-medium mr-4 whitespace-nowrap"
:class="{
'text-[#000000]': isPwdMatched,
'text-[#FF3366]': !isPwdMatched,
}">
{{ i18next.t("AcctMgmt.ConfirmPassword") }}
</span>
<div class="input-and-toggle-btn w-[454px] flex flex-row rounded border border-[1px] relative items-center
h-[40px] mb-4"
:class="{
'border-[#000000]': isPwdMatched,
'border-[#FF3366]': !isPwdMatched,
}">
<input class="outline-none p-1 w-[352px]" :type="isPwdConfirmEyeOn ? 'text' : 'password'"
v-model="inputConfirmPwd"
:class="{
'text-[#000000]': isPwdMatched,
'text-[#FF3366]': !isPwdMatched,
}"/>
<img v-if="isPwdConfirmEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer"
@click="togglePwdConfirmEyeBtn"/>
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer" @click="togglePwdConfirmEyeBtn"/>
</div>
<div class="dummy-grid h-[24px]"></div> <!-- 透過 dummy-grid 來撐起高度-->
<div v-show="!isPwdMatched" 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.PwdNotMatch") }}
</span>
</div>
</div>
</main>
<footer class="flex row footer justify-end pr-[32px]">
<footer class="flex row footer justify-end pr-[32px] pb-8">
<button class="cancel-btn rounded 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"
@click="onCancelBtnClick"
>
{{ i18next.t("Global.Cancel") }}
</button>
<button class="confirm-btn rounded rounded-full w-[92px] h-10 px-2.5 py-6 bg-[#0099FF]
<button class="confirm-btn rounded rounded-full w-[92px] h-10 px-2.5 py-6
flex justify-center items-center text-[#ffffff] font-medium ml-[16px]"
@click="onConfirmBtnClick"
@click="onConfirmBtnClick" :disabled="isConfirmDisabled"
:class="{
'bg-[#0099FF]': !isConfirmDisabled,
'bg-[#E2E8F0]': isConfirmDisabled,
}"
>
{{ i18next.t("Global.Confirm") }}
</button>
@@ -31,15 +98,65 @@
</template>
<script>
import { defineComponent } from 'vue';
import { defineComponent, computed, ref, } from 'vue';
import i18next from "@/i18n/i18n.js";
import { mapActions, } from 'pinia';
import { useModalStore } from '@/stores/modal.js';
import useAcctMgmtStore from '@/stores/acctMgmt.js';
import ModalHeader from "./ModalHeader.vue";
export default defineComponent({
setup() {
const acctMgmtStore = useAcctMgmtStore();
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
const isPwdEyeOn = ref(false);
const isPwdConfirmEyeOn = ref(false);
const isPwdMatched = ref(true);
const inputPwd = ref("");
const inputConfirmPwd = ref("");
const {
id,
} = currentViewingUser;
const isSSO = computed(() => acctMgmtStore.currentViewingUser.isSSO);
const account = computed(() => acctMgmtStore.currentViewingUser.account);
const fullName = computed(() => acctMgmtStore.currentViewingUser.fullName);
const isConfirmDisabled = computed(() => {
return inputPwd.length && inputConfirmPwd.length;
});
const togglePwdEyeBtn = () => {
isPwdEyeOn.value = !isPwdEyeOn.value;
};
const togglePwdConfirmEyeBtn = () => {
isPwdConfirmEyeOn.value = !isPwdConfirmEyeOn.value;
};
const validateConfirmPwd = () => {
isPwdMatched.value = inputPwd.value === inputConfirmPwd.value;
console.log('isPwdMatched.value',isPwdMatched.value );
}
const onConfirmBtnClick = () => {
validateConfirmPwd();
}
return {
isConfirmDisabled,
account,
id,
fullName,
isSSO,
isPwdEyeOn,
isPwdConfirmEyeOn,
togglePwdEyeBtn,
togglePwdConfirmEyeBtn,
isPwdMatched,
inputPwd,
inputConfirmPwd,
onConfirmBtnClick,
};
},
data() {
@@ -51,9 +168,6 @@ export default defineComponent({
ModalHeader,
},
methods: {
onConfirmBtnClick(){
//TODO:
},
onCloseBtnClick(){
this.closeModal();
},

View File

@@ -4,7 +4,7 @@
<ModalHeader :headerText='i18next.t("AcctMgmt.AccountInformation")'/>
<main class="flex main-part flex-col px-6 mt-6">
<h1 id="acct_info_user_name" class="text-[32px] leading-[64px] font-medium">{{ fullName }}</h1>
<div class="status-container">Admin Suspended</div>
<div class="status-container"><Badge displayText="Admin" isActivated/> <Badge displayText="Suspended" :isActivated="false"/></div>
<div id="account_visit_info" class="border-b border-b-[#CBD5E1] border-b-[1px] pb-4">
Account: <span class="text-[#0099FF]">{{ account }}</span>, total visits <span class="text-[#0099FF]">{{ visitTiime }}</span> times.
</div>
@@ -22,8 +22,9 @@
<script>
import i18next from '@/i18n/i18n.js';
import ModalHeader from './ModalHeader.vue';
import useAcctMgmtStore from '@/stores/acctMgmt.js';
import ModalHeader from './ModalHeader.vue';
import Badge from '../../components/Badge.vue';
export default {
setup(){
@@ -51,6 +52,7 @@ export default {
},
components: {
ModalHeader,
Badge,
}
}
</script>