main
vue 181 lines 4.77 KB
Raw
1 <template>
2 <n-button :size :type :quaternary :class="className" @click="showFormDrawer = true">
3 <template #icon>
4 <Icon :name="PasswordIcon" :size="14" />
5 </template>
6 Change Password
7 </n-button>
8
9 <n-drawer
10 v-model:show="showFormDrawer"
11 :width="500"
12 style="max-width: 90vw"
13 :trap-focus="false"
14 display-directive="show"
15 >
16 <n-drawer-content title="Change Password" closable :native-scrollbar="false">
17 <n-spin :show="loading">
18 <n-form ref="formRef" :model :rules>
19 <div class="flex flex-col gap-3">
20 <n-form-item label="Username" required>
21 <n-input :value="username" disabled readonly />
22 </n-form-item>
23 <n-form-item path="password" label="Password">
24 <n-input
25 v-model:value="model.password"
26 type="password"
27 size="large"
28 show-password-on="click"
29 placeholder="At least 8 characters"
30 @keydown.enter="submit"
31 />
32 </n-form-item>
33 <n-form-item path="confirmPassword" label="Confirm Password" first>
34 <n-input
35 v-model:value="model.confirmPassword"
36 type="password"
37 :disabled="!model.password"
38 size="large"
39 show-password-on="click"
40 placeholder="At least 8 characters"
41 @keydown.enter="submit"
42 />
43 </n-form-item>
44
45 <div class="flex justify-end">
46 <n-button type="primary" :disabled="!isValid" @click="submit">Send new password</n-button>
47 </div>
48 </div>
49 </n-form>
50 </n-spin>
51 </n-drawer-content>
52 </n-drawer>
53 </template>
54
55 <script setup lang="ts">
56 import type { ButtonSize, ButtonType, FormInst, FormItemRule, FormRules, FormValidationError } from "naive-ui"
57 import type { User } from "@/types/user"
58 import { NButton, NDrawer, NDrawerContent, NForm, NFormItem, NInput, NSpin, useMessage } from "naive-ui"
59 import PasswordValidator from "password-validator"
60 import { computed, ref, watch } from "vue"
61 import Api from "@/api"
62 import Icon from "@/components/common/Icon.vue"
63 import { useAuthStore } from "@/stores/auth"
64
65 interface ModelType {
66 password: string | null
67 confirmPassword: string | null
68 }
69
70 const { type, size, user, quaternary, className } = defineProps<{
71 user?: User
72 size?: ButtonSize
73 type?: ButtonType
74 quaternary?: boolean
75 className?: string
76 }>()
77
78 const showFormDrawer = ref(false)
79
80 watch(showFormDrawer, () => {
81 clear()
82 })
83
84 const PasswordIcon = "carbon:password"
85 const message = useMessage()
86 const loading = ref(false)
87 const model = ref<ModelType>({
88 password: null,
89 confirmPassword: null
90 })
91 const username = computed(() => user?.username || "")
92 const formRef = ref<FormInst | null>(null)
93 const storedUserName = useAuthStore().userName
94 const passwordSchema = new PasswordValidator()
95
96 passwordSchema
97 .is()
98 .min(8) // Minimum length 8
99 .is()
100 .max(100) // Maximum length 100
101 .has()
102 .uppercase() // Must have uppercase letters
103 .has()
104 .lowercase() // Must have lowercase letters
105 .has()
106 .digits(1) // Must have at least 1 digit
107 .has()
108 .symbols(1) // Must have at least 1 symbol
109
110 const rules: FormRules = {
111 password: [
112 {
113 required: true,
114 trigger: ["blur"],
115 message: "Password is required"
116 },
117 {
118 validator: (_rule: FormItemRule, value: string): boolean => {
119 return !!passwordSchema.validate(value, { details: false })
120 },
121 message:
122 "The string should have a minimum length of 8 characters, minimum of 1 uppercase and lowercase letter, minimum of 1 digit and 1 symbol",
123 trigger: ["blur"]
124 }
125 ],
126 confirmPassword: [
127 {
128 required: true,
129 trigger: ["blur"],
130 message: "Confirm Password is required"
131 },
132 {
133 validator: (_rule: FormItemRule, value: string): boolean => {
134 return value === model.value.password
135 },
136 message: "Password is not same as re-entered password",
137 trigger: ["blur", "password-input"]
138 }
139 ]
140 }
141
142 const isValid = computed(() => {
143 return !!model.value.password && !!model.value.confirmPassword && !!username.value
144 })
145
146 function clear() {
147 model.value.password = null
148 model.value.confirmPassword = null
149 }
150
151 function submit(e: Event) {
152 e.preventDefault()
153 formRef.value?.validate((errors: Array<FormValidationError> | undefined) => {
154 if (!errors) {
155 loading.value = true
156
157 const method = username.value === storedUserName ? "resetOwnPassword" : "resetPassword"
158
159 Api.auth[method](username.value, model.value.password || "")
160 .then(res => {
161 if (res.data.success) {
162 clear()
163 message.success(res.data?.message || "New Password submitted.")
164 } else {
165 message.warning(res.data?.message || "An error occurred. Please try again later.")
166 }
167 })
168 .catch(err => {
169 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
170 })
171 .finally(() => {
172 loading.value = false
173 })
174 } else {
175 for (const err of errors) {
176 message.error(err?.[0]?.message ?? "Invalid fields")
177 }
178 }
179 })
180 }
181 </script>