Now it is time to implement the change password feature for our logged-in users. This lesson is no different than the previous one in terms of complexity, actually a lot simpler since we won't be fetching anything from the server. Our change password form will look like this.
Before we go with implementation let's discuss a few things first. Why can't we have password fields on the same profile page? Well, some pages have that, but this also introduces new problems.
There is no need to have password fields every time user wants to edit account details. Otherwise, it is inevitable that at some point the user gets the validation error message unnecessarily.
Having a separate form to change passwords resolves all the issues above and makes both end-users and developers happy. That being said let's implement the form.
src/stores/changePassword.js
.import { reactive, ref } from "vue";import { defineStore } from "pinia"; export const useChangePassword = defineStore("change-password", () => { const errors = reactive({}); const status = ref(""); const loading = ref(false); const form = reactive({ current_password: "", password: "", password_confirmation: "", }); function resetForm() { form.current_password = ""; form.password = ""; form.password_confirmation = ""; errors.value = {}; status.value = ""; } async function updatePassword() { loading.value = true; errors.value = {}; status.value = ""; return window.axios .put("password", form) .then(() => { status.value = "Password has been updated."; }) .catch((error) => { if (error.response.status === 422) { errors.value = error.response.data.errors; } }) .finally(() => { form.current_password = ""; form.password = ""; form.password_confirmation = ""; loading.value = false; }); } return { form, loading, errors, resetForm, status, updatePassword, };});
src/views/Profile/ChangePassword.vue
component.<script setup>import { onBeforeUnmount } from "vue";import { useChangePassword } from "@/stores/changePassword"; const store = useChangePassword(); onBeforeUnmount(store.resetForm);</script> <template> <form @submit.prevent="store.updatePassword"> <div class="flex flex-col mx-auto md:w-96 w-full"> <h1 class="text-2xl font-bold mb-4 text-center">Change password</h1> <div class="alert alert-success mb-4" v-show="store.status"> {{ store.status }} </div> <div class="flex flex-col gap-2 mb-4"> <label for="current_password" class="required">Current password</label> <input type="password" name="current_password" id="current_password" class="form-input" v-model="store.form.current_password" :disabled="store.loading" /> <ValidationError :errors="store.errors" field="current_password" /> </div> <div class="flex flex-col gap-2 mb-4"> <label for="password" class="required">New password</label> <input type="password" name="password" id="password" class="form-input" v-model="store.form.password" :disabled="store.loading" /> <ValidationError :errors="store.errors" field="password" /> </div> <div class="flex flex-col gap-2"> <label for="password_confirmation" class="required"> Confirm new password </label> <input type="password" name="password_confirmation" id="password_confirmation" class="form-input" v-model="store.form.password_confirmation" :disabled="store.loading" /> </div> <div class="border-t h-[1px] my-6"></div> <button type="submit" class="btn btn-primary" :disabled="store.loading"> <IconSpinner class="animate-spin" v-show="store.loading" /> Update password </button> </div> </form></template>
src/router/index.js
.{ path: "/profile/change-password", name: "profile.change-password", beforeEnter: auth, component: () => import("@/views/Profile/ChangePassword.vue"),},
The entire content of the src/router/index.js
file looks like this.
import { createRouter, createWebHistory } from "vue-router"; function auth(to, from, next) { if (!localStorage.getItem("access_token")) { return next({ name: "login" }); } next();} function guest(to, from, next) { if (localStorage.getItem("access_token")) { return next({ name: "vehicles.index" }); } next();} const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: "/", name: "home", component: import("@/views/HomeView.vue"), }, { path: "/register", name: "register", beforeEnter: guest, component: () => import("@/views/Auth/RegisterView.vue"), }, { path: "/login", name: "login", beforeEnter: guest, component: () => import("@/views/Auth/LoginView.vue"), }, { path: "/profile", name: "profile.edit", beforeEnter: auth, component: () => import("@/views/Profile/EditView.vue"), }, { path: "/profile/change-password", name: "profile.change-password", beforeEnter: auth, component: () => import("@/views/Profile/ChangePassword.vue"), }, { path: "/vehicles", name: "vehicles.index", beforeEnter: auth, component: () => import("@/views/Vehicles/IndexView.vue"), }, ],}); export default router;
src/App.vue
.<RouterLink class="router-link" :to="{ name: 'profile.change-password' }"> Change password</RouterLink>
Our src/App.vue
component should be like that now.
<script setup>import { RouterLink, RouterView } from "vue-router";import { useAuth } from "@/stores/auth"; const auth = useAuth();</script> <template> <header class="py-6 bg-gray-100 shadow"> <div class="container md:px-2 px-4 mx-auto"> <nav class="flex gap-4 justify-between"> <div class="flex gap-4 items-center"> <h2 class="text-xl font-bold"> <div class="inline-flex items-center justify-center bg-blue-600 w-6 h-6 text-center text-white rounded" > P </div> myParking </h2> <template v-if="auth.check"> <RouterLink class="router-link" :to="{ name: 'vehicles.index' }"> Vehicles </RouterLink> </template> <template v-else> <RouterLink class="router-link" :to="{ name: 'home' }"> Home </RouterLink> </template> </div> <div class="flex gap-4 items-center"> <template v-if="auth.check"> <RouterLink class="router-link" :to="{ name: 'profile.edit' }"> Profile </RouterLink> <RouterLink class="router-link" :to="{ name: 'profile.change-password' }" > Change password </RouterLink> <button @click="auth.logout" class="router-link">Logout</button> </template> <template v-else> <RouterLink class="router-link" :to="{ name: 'login' }"> Login </RouterLink> <RouterLink class="router-link" :to="{ name: 'register' }"> Register </RouterLink> </template> </div> </nav> </div> </header> <div class="container md:px-2 px-4 pt-8 md:pt-16 mx-auto"> <RouterView /> </div></template>