blob: 31a4a2c512b9761c17f35452e9d61169e47cdf96 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="user-layout-wrapper">
<div class="container">
<div class="user-layout-content">
<a-card :bordered="false" class="force-password-card">
<template #title>
<div style="text-align: center; font-size: 18px; font-weight: bold;">
{{ $t('label.action.change.password') }}
</div>
<div v-if="!isSubmitted" style="text-align: center; font-size: 14px; color: #666; margin-top: 5px;">
{{ $t('message.change.password.required') }}
</div>
</template>
<a-spin :spinning="loading">
<div v-if="isSubmitted" class="success-state">
<check-outlined class="success-icon" />
<div class="success-text">
{{ $t('message.success.change.password') }}
</div>
<div class="success-subtext">
{{ $t('message.please.login.new.password') }}
</div>
<a-button
type="primary"
size="large"
block
@click="redirectToLogin()"
style="margin-top: 20px;"
>
{{ $t('label.login') }}
</a-button>
</div>
<a-form
v-else
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
@finish="handleSubmit"
v-ctrl-enter="handleSubmit"
>
<a-form-item name="currentpassword" :label="$t('label.currentpassword')">
<a-input-password
v-model:value="form.currentpassword"
:placeholder="$t('label.currentpassword')"
size="large"
v-focus="true"
/>
</a-form-item>
<a-form-item name="password" :label="$t('label.new.password')">
<a-input-password
v-model:value="form.password"
:placeholder="$t('label.new.password')"
size="large"
/>
</a-form-item>
<a-form-item name="confirmpassword" :label="$t('label.confirmpassword')">
<a-input-password
v-model:value="form.confirmpassword"
:placeholder="$t('label.confirmpassword')"
size="large"
/>
</a-form-item>
<a-form-item>
<a-button
html-type="submit"
type="primary"
size="large"
block
:disabled="loading"
:loading="loading"
@click="handleSubmit"
>
{{ $t('label.ok') }}
</a-button>
</a-form-item>
<div class="actions">
<a @click="logoutAndRedirectToLogin()">{{ $t('label.logout') }}</a>
</div>
</a-form>
</a-spin>
</a-card>
</div>
</div>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { postAPI } from '@/api'
import Cookies from 'js-cookie'
import { PASSWORD_CHANGE_REQUIRED } from '@/store/mutation-types'
export default {
name: 'ForceChangePassword',
data () {
return {
loading: false,
isSubmitted: false
}
},
created () {
this.formRef = ref()
this.form = reactive({})
this.isPasswordChangeRequired()
},
computed: {
rules () {
return {
currentpassword: [{ required: true, message: this.$t('message.error.current.password') }],
password: [
{ required: true, message: this.$t('message.error.new.password') },
{ validator: this.validateNewPassword, trigger: 'change' }
],
confirmpassword: [
{ required: true, message: this.$t('message.error.confirm.password') },
{ validator: this.validateTwoPassword, trigger: 'change' }
]
}
}
},
methods: {
async validateNewPassword (rule, value) {
const currentPassword = this.form.currentpassword
if (!value || value.length === 0) {
return Promise.resolve()
}
// Ensure new password is different from current password
if (currentPassword && value === currentPassword) {
return Promise.reject(this.$t('message.error.newpassword.sameascurrent'))
}
return Promise.resolve()
},
async validateTwoPassword (rule, value) {
if (!value || value.length === 0) {
return Promise.resolve()
} else if (rule.field === 'confirmpassword') {
const form = this.form
const messageConfirm = this.$t('message.validate.equalto')
const passwordVal = form.password
if (passwordVal && passwordVal !== value) {
return Promise.reject(messageConfirm)
} else {
return Promise.resolve()
}
} else {
return Promise.resolve()
}
},
handleSubmit (e) {
e.preventDefault()
if (this.loading) return
this.formRef.value.validate().then(() => {
this.loading = true
const values = toRaw(this.form)
const userId = Cookies.get('userid')
const params = {
id: userId,
password: values.password,
currentpassword: values.currentpassword
}
postAPI('updateUser', params).then(async () => {
this.$localStorage.remove(PASSWORD_CHANGE_REQUIRED)
await this.handleLogout()
this.isSubmitted = true
}).catch(error => {
console.error(error)
this.$message.error(this.$t('message.error.change.password'))
}).finally(() => {
this.loading = false
})
}).catch(error => {
console.log('Validation failed:', error)
})
},
async handleLogout () {
try {
await this.$store.dispatch('Logout')
} catch (e) {
console.error('Logout failed:', e)
} finally {
Cookies.remove('userid')
Cookies.remove('token')
}
},
redirectToLogin () {
this.$router.replace('/user/login')
},
logoutAndRedirectToLogin () {
this.handleLogout().then(() => {
this.redirectToLogin()
})
},
async isPasswordChangeRequired () {
const passwordChangeRequired = this.$localStorage.get(PASSWORD_CHANGE_REQUIRED)
this.isSubmitted = !passwordChangeRequired
}
}
}
</script>
<style scoped lang="less">
.user-layout-wrapper {
display: flex;
justify-content: center;
align-items: center;
.container {
width: 100%;
padding: 16px;
.user-layout-content {
display: flex;
justify-content: center;
.force-password-card {
width: 100%;
max-width: 420px;
border-radius: 8px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
}
}
}
}
.actions {
text-align: center;
margin-top: 16px;
a {
color: #1890ff;
transition: color 0.3s;
&:hover {
color: #40a9ff;
}
}
}
.success-state {
text-align: center;
padding: 20px 0;
.success-icon {
font-size: 48px;
color: #52c41a;
margin-bottom: 16px;
}
.success-text {
font-size: 20px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.success-subtext {
font-size: 14px;
color: #666;
}
}
</style>