Skip to content

Commit

Permalink
feat: added change password form
Browse files Browse the repository at this point in the history
  • Loading branch information
Tbaile committed Nov 2, 2023
1 parent dee343a commit f7fceb4
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 19 deletions.
Binary file removed src/assets/logo.png
Binary file not shown.
16 changes: 16 additions & 0 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,19 @@
html {
@apply text-gray-900 dark:text-gray-50;
}

h2 {
@apply text-2xl font-medium;
}

h3 {
@apply text-lg font-medium;
}

hr {
@apply border-gray-200 dark:border-gray-700;
}

.description-text {
@apply text-sm font-normal text-gray-500 dark:text-gray-400;
}
3 changes: 0 additions & 3 deletions src/components/LandingPage.vue

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/SidebarMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface MenuEntry {
const entries: Array<MenuEntry> = [
{
label: 'base_template.account_settings',
label: 'account_settings.title',
route_name: 'user_account',
icon: faGear
}
Expand Down
21 changes: 19 additions & 2 deletions src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
"network": "Network error"
},
"base_template": {
"logout": "Logout",
"account_settings": "Account settings"
"logout": "Logout"
},
"login_form": {
"welcome": "Welcome",
Expand All @@ -15,5 +14,23 @@
"remember_me": "Remember me",
"sign_in": "Sign in",
"invalid_credentials": "Invalid credentials"
},
"account_settings": {
"title": "Account settings",
"change_password": "Change password",
"change_password_description": "Setting a new password for your account will affect all connected services:",
"nethvoice": "Nethvoice CTI",
"nethservice": "Nethservice COLLABORATION",
"current_password": "Current password",
"new_password": "New password",
"confirm_password": "Confirm new password",
"save_password": "Save password",
"passwords_mismatch": "Passwords mismatch",
"invalid_credentials": "Wrong password",
"password_minimum_age": "Too little time has passed since the last password change",
"password_complexity": "Password is too simple",
"password_length": "Password too short",
"password_history": "Password already used",
"generic_error": "Can't change password right now, please try again later"
}
}
25 changes: 25 additions & 0 deletions src/lib/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export class MessageBag extends Map<string, Array<string>> {
/**
* Append a record to the bag.
* @param key
* @param message
*/
append(key: string, message: string) {
if (!this.has(key)) {
this.set(key, [])
}
this.get(key)!.push(message)
}

/**
* Helper function that is compatible with vuei18n.
* @param key
*/
getFirstMessage(key: string): string {
if (this.has(key)) {
return this.get(key)![0]
} else {
return ''
}
}
}
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ app.use(router)

app.use(
createI18n({
legacy: false,
locale: navigator.language,
fallbackLocale: 'en-US',
messages: {
Expand Down
2 changes: 1 addition & 1 deletion src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const router = createRouter({
{
path: 'user/account',
name: 'user_account',
component: () => import('../views/HomeView.vue'),
component: () => import('../views/UserAccount.vue'),
beforeEnter: () => {
const { previouslyLogged } = useAuth()
if (!previouslyLogged.value) {
Expand Down
3 changes: 0 additions & 3 deletions src/stores/useNotifications.ts

This file was deleted.

9 changes: 0 additions & 9 deletions src/views/HomeView.vue

This file was deleted.

156 changes: 156 additions & 0 deletions src/views/UserAccount.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<script lang="ts" setup>
import { NeButton, NeInlineNotification, NeTextInput } from '@nethserver/vue-tailwind-lib'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faSave } from '@fortawesome/free-solid-svg-icons'
import { ref } from 'vue'
import axios from 'axios'
import { MessageBag } from '@/lib/validation'
import { useI18n } from 'vue-i18n'
interface ChangePasswordResponse {
status: 'success' | 'failure'
message: string
}
const oldPassword = ref('')
const newPassword = ref('')
const confirmPassword = ref('')
const loading = ref(false)
const errorMessage = ref<string>()
const validationMessages = ref(new MessageBag())
const { t } = useI18n()
function validate(): boolean {
validationMessages.value.clear()
if (newPassword.value != confirmPassword.value) {
validationMessages.value.append('confirm_password', t('account_settings.passwords_mismatch'))
}
return validationMessages.value.size < 1
}
async function changePassword() {
loading.value = true
errorMessage.value = undefined
try {
if (validate()) {
const response = await axios.post<ChangePasswordResponse>('/api/change-password', {
current_password: oldPassword.value,
new_password: newPassword.value
})
if (response.data.status == 'success') {
oldPassword.value = ''
newPassword.value = ''
confirmPassword.value = ''
} else {
switch (response.data.message) {
case 'error_invalid_credentials':
validationMessages.value.append(
'old_password',
t('account_settings.invalid_credentials')
)
break
case 'error_password_complexity':
validationMessages.value.append(
'new_password',
t('account_settings.password_complexity')
)
break
case 'error_password_length':
validationMessages.value.append('new_password', t('account_settings.password_length'))
break
case 'error_password_history':
validationMessages.value.append('new_password', t('account_settings.password_history'))
break
case 'error_password_minimum_age':
validationMessages.value.append(
'new_password',
t('account_settings.password_minimum_age')
)
break
default:
errorMessage.value = 'account_settings.generic_error'
}
}
}
} catch (exception: any) {
if (axios.isAxiosError(exception) && exception.response != undefined) {
if (exception.response.status >= 500) {
errorMessage.value = 'login_form.server_error'
}
} else {
errorMessage.value = exception.message
}
} finally {
loading.value = false
}
}
</script>

<template>
<div class="space-y-8">
<h2>{{ $t('account_settings.title') }}</h2>
<hr />
<div class="grid max-w-3xl grid-cols-1 gap-x-6 gap-y-6 lg:grid-cols-2">
<div class="space-y-1">
<h3>
{{ $t('account_settings.change_password') }}
</h3>
<p class="description-text">{{ $t('account_settings.change_password_description') }}</p>
<ul class="description-text list-disc pl-6">
<li>{{ $t('account_settings.nethvoice') }}</li>
<li>{{ $t('account_settings.nethservice') }}</li>
</ul>
</div>
<form class="flex flex-col space-y-8" @submit.prevent="changePassword()">
<NeInlineNotification
v-if="errorMessage"
:title="$t(`errors.${errorMessage}`)"
kind="error"
/>
<NeTextInput
v-model="oldPassword"
:disabled="loading"
:invalid-message="validationMessages.getFirstMessage('old_password')"
:label="$t('account_settings.current_password')"
autocomplete="current-password"
autofocus
is-password
name="old_password"
required
/>
<NeTextInput
v-model="newPassword"
:disabled="loading"
:invalid-message="validationMessages.getFirstMessage('new_password')"
:label="$t('account_settings.new_password')"
autocomplete="new-password"
is-password
name="new_password"
required
/>
<NeTextInput
v-model="confirmPassword"
:disabled="loading"
:invalid-message="validationMessages.getFirstMessage('confirm_password')"
:label="$t('account_settings.confirm_password')"
autocomplete="new-password"
is-password
name="confirm_password"
required
/>
<NeButton
:disabled="loading"
:loading="loading"
class="ml-auto"
kind="primary"
type="submit"
>
<FontAwesomeIcon :icon="faSave" aria-hidden="true" class="mr-2 h-4 w-4" />
{{ $t('account_settings.save_password') }}
</NeButton>
</form>
</div>
</div>
</template>

0 comments on commit f7fceb4

Please sign in to comment.