I have a VueJS/Vuex frontend consuming an express/postgres api.
In Postman, both registration and login work with a request like:
{ "user": { "email": "user#gmail", "password":...}}
From the Vue app, registration works as expected, but for login, instead of sending a request object like
{ "user": { "email": "user#gmail", "password":...}}
, which is what the api is expecting, it is sending only,
{ "email": "user#gmail", "password":...}
This results in the api throwing:
TypeError: Cannot read property 'email' of undefined
Here is my Login component:
<template>
<div class="ui stackable three column centered grid container">
<div class="column">
<h2 class="ui dividing header">Log In</h2>
<Notification
:message="notification.message"
:type="notification.type"
v-if="notification.message"
/>
<form class="ui form" #submit.prevent="login">
<div class="field">
<label>Email</label>
<input type="email" name="email" v-model="email" placeholder="Email" required>
</div>
<div class="field">
<label>Password</label>
<input type="password" name="password" v-model="password" placeholder="Password" required>
</div>
<button class="fluid ui primary button">LOG IN</button>
<div class="ui hidden divider"></div>
</form>
<div class="ui divider"></div>
<div class="ui column grid">
<div class="center aligned column">
<p>
Don't have an account? <router-link to="/signup">Sign Up</router-link>
</p>
</div>
</div>
</div>
</div>
</template>
<script>
import Notification from '#/components/Notification'
export default {
name: 'LogIn',
components: {
Notification
},
data () {
return {
email: '',
password: '',
notification: {
message: '',
type: ''
}
}
},
// beforeRouteEnter (to, from, next) {
// const token = localStorage.getItem('tweetr-token')
// return token ? next('/') : next()
// },
methods: {
login () {
this.$store
.dispatch('login', {
email: this.email,
password: this.password
})
.then(() => {
console.log(this.$store.user)
// redirect to user home
this.$router.push('/')
})
.catch(error => console.log(error))
}
}
}
</script>
And this is my store.js:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
// import SubscriptionsService from './services/SubscriptionsService'
Vue.use(Vuex)
const VUE_APP_ROOT_API = 'http://localhost:8000'
export default new Vuex.Store({
state: {
status: '',
user: JSON.parse(localStorage.getItem('user'))
},
mutations: {
auth_request (state) {
state.status = 'Signing in...'
},
set_user (state, user) {
state.user = user
localStorage.setItem('user', JSON.stringify(user))
console.log(user)
},
auth_success (state) {
state.status = 'success'
},
auth_error (state) {
state.status = 'Invalid credentials'
},
logout (state) {
state.status = ''
state.user = null
localStorage.removeItem('user')
}
},
actions: {
register ({ commit }, user) {
return new Promise((resolve, reject) => {
commit('auth_request')
console.log(process.env.VUE_APP_ROOT_API)
axios({ url: VUE_APP_ROOT_API + '/api/auth', data: user, method: 'POST' })
.then(async resp => {
const user = resp.data.user
commit('auth_success')
commit('set_user', user)
resolve(resp)
})
.catch(err => {
commit('auth_error', err)
localStorage.removeItem('token')
reject(err)
})
})
},
login ({ commit }, user) {
return new Promise((resolve, reject) => {
commit('auth_request')
console.log(user);
axios({ url: VUE_APP_ROOT_API + '/api/auth/login', data: user, method: 'POST' })
.then(resp => {
const user = resp.data.user
// console.log(user)
// console.log(resp)
commit('auth_success')
commit('set_user', user)
resolve(resp)
})
.catch(err => {
commit('auth_error')
commit('logout')
reject(err)
})
})
},
logout ({ commit }) {
return new Promise((resolve) => {
commit('logout')
localStorage.removeItem('token')
delete axios.defaults.headers.common['authorization']
resolve()
})
}
},
getters: {
isAuthenticated: state => !!state.user,
authStatus: state => state.status,
user: state => state.user
}
})
for comparison, here is my working SignUp component:
<template>
<div class="ui stackable three column centered grid container">
<div class="column">
<h2 class="ui dividing header">Sign Up, it's free!</h2>
<form class="ui form" #submit.prevent="signup">
<div class="field">
<label>Username</label>
<input type="username" name="username" v-model="username" placeholder="Username">
</div>
<div class="field">
<label>Email</label>
<input type="email" name="email" v-model="email" placeholder="Email">
</div>
<div class="field" >
<label>Password</label>
<input type="password" name="password" v-model="password" placeholder="Password">
</div>
<button class="fluid ui primary button">SIGN UP</button>
<div class="ui hidden divider"></div>
</form>
<div class="ui divider"></div>
<div class="ui column grid">
<div class="center aligned column">
<p>
Got an account? <router-link to="/login">Log In</router-link>
</p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SignUp',
data () {
return {
email: '',
password: '',
username: '',
notification: {
message: '',
type: ''
}
}
},
methods: {
signup: function () {
let data = {
user: {
email: this.email,
password: this.password,
username: this.username
}
}
this.$store.dispatch('register', data)
.then(() => this.$router.push('/'))
.catch(err => console.log(err))
},
}
}
</script>
How do I format the request object to match what the api expects?
Try sending the payload in a similar structure you have for your sign up function:
.dispatch('login', {
user: {
email: this.email,
password: this.password
}
})
Related
I'm working on a website that uses vue and firebase. After the authentication part the user gets to the dashboard where there is the list of his projects that are a subcollection of the user document in firestore.
I made a pinia store that manages this data and each time a project is created with the form or gets deleted the state.projects updates with the new array of projects that gets cycled to display the list in the view.
Inside the view I have access to the store.projects thanks to a getter that should be reactive but when I add or delete a project nothing happens in the view, but still the state.projects gets updated.
here is the code of the DashboardView.vue:
<template>
<MainHeader mode="dashboard" />
<main class="main">
<div class="main__container">
<section class="main__section">
<div class="section__header">
<h1 class="header__title">Projects</h1>
<!-- <TextInput type="text" placeholder="Search" v-model="filter" /> -->
</div>
<div class="section__content">
<ul class="content__list">
<li
v-for="project in projects"
:key="project.id"
class="content__item"
>
{{ project.id }}
<!-- <router-link
:to="{ name: 'ProjectView', params: { id: project.id} }">
</router-link> -->
<SimpleButton #click="deleteProject(project.id)" type="button" text="delete" />
</li>
</ul>
</div>
<div class="section__footer">
<form #submit.prevent="createProject">
<TextInput type="text" placeholder="name" v-model="form.id" />
<TextInput type="text" placeholder="website" v-model="form.website" />
<SimpleButton type="submit" text="Add" />
</form>
</div>
</section>
</div>
</main>
</template>
<script>
import { useUserDataStore } from "../stores/UserDataStore.js";
import MainHeader from "../components/MainHeader.vue";
import SimpleButton from "../components/SimpleButton.vue";
import TextInput from "../components/TextInput.vue";
import { ref } from '#vue/reactivity';
export default {
name: "DashboardView",
components: {
MainHeader,
SimpleButton,
TextInput,
},
setup() {
// const filter = "";
const form = ref({});
const userDataStore = useUserDataStore();
const projects = userDataStore.getProjects;
const createProject = () => {
userDataStore.createProject(form.value)
}
const deleteProject = (id) => {
userDataStore.deleteProject(id)
}
return {
projects,
form,
createProject,
deleteProject,
};
},
};
</script>
And here the pinia store code:
import { defineStore } from "pinia";
import router from "../router";
import { db } from '../firebase';
import { doc, setDoc, getDoc, getDocs, collection, deleteDoc } from 'firebase/firestore'
export const useUserDataStore = defineStore('UserDataStore', {
state: () => {
userData: { }
projects: []
uid: null
},
actions: {
createNewUser(uid, name) {
setDoc(doc(db, "users", uid), {
name
})
.then(() => {
this.fetchUserData(uid)
})
.catch((error) => console.log(error))
},
fetchUserData(uid) {
this.uid = uid
// Fetch user doc with uid
getDoc(doc(db, "users", uid))
.then((response) => {
this.userData = response.data()
// Fetch user projects
getDocs(collection(db, "users", uid, "projects"))
.then((response) => {
const projectsArray = []
response.forEach(el => {
projectsArray.push({ data: el.data(), id: el.id})
})
this.projects = projectsArray
console.log(this.projects);
router.push({ name: 'DashboardView' })
})
})
.catch((error) => console.log(error))
},
createProject(details) {
const { id, website } = details
setDoc(doc(db, "users", this.uid, "projects", id), {
website
}).then(() => {
console.log('created');
this.fetchUserData(this.uid)
})
.catch((err) => console.log(err))
},
deleteProject(id) {
deleteDoc(doc(db, "users", this.uid, "projects", id))
.then(() => {
console.log('deleted');
this.fetchUserData(this.uid);
})
.catch(err => console.log(err))
}
},
getters: {
getProjects: (state) => state.projects
}
})
A store is reactive object, the reactivity of store property is disabled at the time when it's accessed in setup function:
const projects = userDataStore.getProjects;
It should be either:
const projects = computed(() => userDataStore.getProjects);
Or:
const { getProjects: projects } = storeToRefs(userDataStore);
I'm a newbie in VueX. I face a problem which is after I updated the profile information and save it successfully, the database has shown the latest updated info but the user interfaces cannot retrieve the latest data. The state seems no updated.
My profile UI
https://i.stack.imgur.com/8QtI9.png
but after updated, all the info disappear and didn't show the latest info
https://i.stack.imgur.com/ul1ob.png
UserProfile.vue
<template>
<div class="container" style="padding-top:25px">
<div class="main-font">My Profile</div>
<div class="d-flex row">
<div class="col-6">
<ValidationObserver v-slot="{ handleSubmit }">
<form #submit.prevent="handleSubmit(updateProfile)">
<div class="d-flex py-4">
<div>
<img class="profile" src="/img/default.png" alt=""
</div>
<div class="my-auto ml-5">
<button type="submit" class="btn upload text"><i class="fas fa-upload fa-sm pr-2"></i>Upload new picture</button>
</div>
</div>
<div class="form-group col-10 p-0 m-0">
<ValidationProvider name="Name" rules="required|alpha" v-slot="{ errors }">
<label class="text">Name</label>
<input type="text" id="name" class="form-control form-text" placeholder="Enter your username" v-model="userForm.name">
<span class="error-messsage">{{ errors[0] }}</span>
</ValidationProvider>
<!-- <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small> -->
</div>
<div class="form-group col-10 p-0 m-0 mt-4">
<ValidationProvider name="E-mail" rules="required|email" v-slot="{ errors }">
<label class="text">Email</label>
<input type="email" id="email" class="form-control form-text" placeholder="Enter email" v-model="userForm.email">
<span class="error-messsage">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<button type="submit" class="btn col-10 p-o save-but">SAVE CHANGES</button>
</form>
</ValidationObserver>
</div>
<div class="w-50">
<img class="bg-img" src="/img/profile-bg.png" alt="">
</div>
</div>
</div>
script
<script>
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate/dist/vee-validate.full';
export default {
components: {
ValidationProvider,
ValidationObserver,
},
data() {
return {
userForm: {
name: '',
email: '',
},
error: null,
}
},
created () {
this.userForm = JSON.parse(JSON.stringify(this.$store.getters.currentUser));
},
computed: {
currentUser(){
return this.$store.getters.currentUser;
},
},
methods: {
getUser (){
const token = this.$store.getters.currentUser.token
axios.get('/api/auth/userprofile',{
headers: {
Authorization: `Bearer ${token}`
}
})
.then(response => {
this.userForm= response.data.user;
})
},
updateProfile () {
const token = this.$store.getters.currentUser.token
// console.log(this.$store.getters.currentUser.token)
axios.put('/api/auth/update-profile',
{
name: this.userForm.name,
email: this.userForm.email,
},
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json" // add content-type
}
})
.then(response => {
this.userForm.name = response.data.name;
this.userForm.email = response.data.email;
swal({
icon: "success",
text: "Update Succesfully!",
});
// this.$store.commit('update');
})
}
}
}
</script>
store.js
import {getLoggedinUser} from './auth';
import {getUser} from './auth';
const user = getLoggedinUser();
const updateUser = getUser();
export default {
state: {
currentUser: user,
isLoggedin: !!user,
loading: false,
auth_error: null,
reg_error:null,
registeredUser: null,
update: null,
},
getters: {
isLoading(state){
return state.loading;
},
isLoggedin(state){
return state.isLoggedin;
},
currentUser(state){
return state.currentUser;
},
authError(state){
return state.auth_error;
},
regError(state){
return state.reg_error;
},
registeredUser(state){
return state.registeredUser;
},
update(state){
return state.update;
}
},
mutations: {
login(state){
state.loading = true;
state.auth_error = null;
},
loginSuccess(state, payload){
state.auth_error = null;
state.isLoggedin = true;
state.loading = false;
state.currentUser = Object.assign({}, payload.user , {token: payload.access_token});
localStorage.setItem("user", JSON.stringify(state.currentUser));
},
loginFailed(state, payload){
state.loading = false;
state.auth_error = payload.error;
},
logout(state){
localStorage.removeItem("user");
state.isLoggedin = false;
state.currentUser = null;
},
registerSuccess(state, payload){
state.reg_error = null;
state.registeredUser = payload.user;
},
registerFailed(state, payload){
state.reg_error = payload.error;
},
update(state, payload) {
state.currentUser = payload.data;
}
},
actions: {
login(context){
context.commit("login");
},
// update(context){
// // state.currentUser.update(context);
// }
}
};
auth.js
export function registerUser(credentials){
return new Promise((res,rej)=>{
axios.post('/api/auth/register', credentials)
.then(response => {
res(response.data);
})
.catch(err => {
rej('An error occured.. try again later.')
})
})
}
export function login(credentials){
return new Promise((res,rej)=>{
axios.post('/api/auth/login', credentials)
.then(response => {
setAuthorization(response.data.access_token);
res(response.data);
})
.catch(err => {
rej('Wrong Email/Password combination.')
})
})
}
export function getLoggedinUser(){
const userStr = localStorage.getItem('user');
if(!userStr){
return null
}
return JSON.parse(userStr);
}
export function getUser(credentials){
return new Promise((res,rej)=>{
axios.get('/api/auth/userprofile', credentials)
.then(response => {
// setAuthorization(response.data.access_token);
res(response.data);
})
.catch(err => {
rej('No User')
})
})
}
Please help me if you have any ideas or solution.
You're not running any dispatches of your store.
You have defined update mutation, but commented it.
Uncomment this
// this.$store.commit('update')
and modify it like so:
this.$store.commit('update', { data: response.data })
Next do the same for your login request.
When the client clicks on the button, I want him to block another client, so I want my id to be dynamic: http://example/user/:id.
My template:
<template>
<div class>
<div v-for="post in posts" :key="post.id">
<div>
<div>{{ post.name }}</div>
<div>{{ post.id }}</div>
<button #click='BlockUser'>Block</button>
</div>
</div>
</div>
</template>
And my script:
<script>
const axios = require('axios');
export default {
name: 'User',
data() {
return {
posts: [],
errors: [],
id: {
id: ""
},
}
},
methods: {
getData() {
axios.get(`http://example/user`)
.then(result => {
this.posts = result.data
console.log(result)
})
},
BlockUser() {
axios.get('http://example/user/blacklist/:id' + encodeURIComponent(this.id.id))
.then(response => {
console.log(response)
})
},
},
}
</script>
Initially, I set a value for the id in data id a number and it worked. But now that I put an empty string.
it returns an undefined
If your posts have user_id field inside, you can pass it to BlockUser method, and use it in the request url.
<button #click='BlockUser(post.user_id)'>Block</button>
BlockUser(user_id) {
axios.get(`http://example/user/blacklist/${user_id}`)
.then(response => {
console.log(response)
})
},
I have a simple file upload component FileUpload.vue
<template>
<div>
<input type="file" name="file" id="file" v-on:change="handleFileUpload()" ref="file">
<button class="btn shadow-lg first" #click="addFiles()">SELECT FILE</button>
<button class="btn shadow-lg" v-on:click="handleSubmit()">UPLOAD</button>
<p> {{uploadPercentage}} </p>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'FileUpload',
data() {
return {
file: '',
uploadPercentage: 0
}
},
methods: {
handleFileUpload: () => {
this.file = document.getElementById('file').files[0];
},
addFiles: () => {
document.getElementById('file').click();
},
handleSubmit: () => {
let formData = new FormData();
formData.append('file', this.file);
axios.post('/file/create',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
console.log(this.uploadPercentage);
this.uploadPercentage = parseInt(Math.round(progressEvent.loaded * 100 / progressEvent.total));
}
}
)
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log(error);
})
}
}
}
</script>
The data uploadPercentage is initialized to 0. As the file upload starts, the uploadPercentage value changes in the onUploadProgress method.
If I console log uploadPercentage, it shows the changes in the value. It goes from 0 to 100.
console.log(this.uploadPercentage)
But in my view, uploadPercentage never changes. It always shows the initial value 0.
<p> {{uploadPercentage}} </p>
What am I doing wrong?
Thanks in advance.
I store the log in status of the user in my store.js (using vuex for state management)
When the user is logged in the login status is set to true in store.js
I check if the user is logged in and using v-if i hide the login button . Till he everything works fine
Now for checking purpose i removed the v-if condition on login button
I set up á before enter navigation guard in my !ogin.vue component as below
login.vue
beforeRouteEnter(to, from, next){
next(vm => {
if(vm.$store.getters.g_loginStatus === true){
next('/');
}else{
next();
}
})
}
If the user is logged in and presses the login button he is redirected to the home page
This works fine as the navigation guard is set up.
but the problem arises when i directly type in the login component url (localhost:8080/login) in the search.
The login component gets loaded normally without getting redirected to home page...
Why does this happen¿ Am i doing something wrong
I enen tried another approach using route meta fields following the documentation at route meta fields
But same problem
when i type the direct url to login component in search not getting redirected
import Vue from 'vue'
import Vuex from 'vuex'
import * as firebase from 'firebase'
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
loggedIn: false,
userName: 'Guest',
error: {
is: false,
errorMessage: ''
},
toast: {
is: false,
toastMessage: ''
}
},
getters: {
g_loginStatus: state => {
return state.loggedIn;
},
g_userName: state => {
return state.userName;
},
g_error: state => {
return state.error;
},
g_toast: (state) => {
return state.toast;
}
},
mutations: {
m_logInUser: (state) => {
state.loggedIn = true;
},
m_loggedOut: (state) => {
state.loggedIn = false;
}
},
actions: {
a_logInUser: ({state, dispatch}, user) => {
return new Promise((resolve, reject) => {
firebase.auth().signInWithEmailAndPassword(user.e, user.p).then(
() =>{
resolve(dispatch('a_authStateObserver'));
}, error => {
state.error.is = true;
let errorCode = error.code;
let errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
state.error.errorMessage = 'Wrong password.';
} else {
state.errorMessage = errorMessage;
}
}
);
});
},
a_loggedOut: () => {
firebase.auth().signOut().then(() => {
dispatch('a_authStateObserver');
});
},
a_signUpUser: ({state, dispatch}, user) => {
return new Promise((resolve, reject) => {
firebase.auth().createUserWithEmailAndPassword(user.e, user.p).then(
(u) =>{
let uid = u.uid;
resolve(dispatch('a_authStateObserver'));
}, error => {
state.error.is = true;
let errorCode = error.code;
let errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
state.error.errorMessage = 'Wrong password.';
} else {
state.errorMessage = errorMessage;
}
}
);
});
},
a_authStateObserver: ({commit, state}) => {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
var displayName = user.displayName;
state.userName = user.email;
state.error.is = false;
commit('m_logInUser');
} else {
// User is signed out.
commit('m_loggedOut');
}
});
}
}
});
login.vue
<template>
<div class="container">
<div class="row">
<div class="form_bg center-block">
<form #submit.prevent="loginUser">
<h3 class="text-center">Log in</h3>
<br/>
<div class="form-group">
<input v-model="email" type="email" class="form-control" placeholder="Your Email">
</div>
<div class="form-group">
<input v-model="password" type="password" class="form-control" placeholder="Password">
</div>
<div class="align-center">
<p class="error" v-if="g_error.is">{{ g_error.errorMessage }}</p>
<button type="submit" class="btn btn-success center-block">Log in</button>
</div>
</form>
<br>
<p style="display:inline-block">Don't have an account?</p>
<router-link to="/signup" tag="a" style="display:inline-block">Sign up</router-link>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default{
data(){
return{
email: '',
password: ''
};
},
methods: {
loginUser(){
this.$store.dispatch('a_logInUser', {e: this.email, p: this.password}).then(() =>{
this.$router.replace('/statuses');
});
}
},
computed: {
...mapGetters([
'g_error'
])
},
beforeRouteEnter(to, from, next){
next(vm => {
console.log(vm.$store.getters.g_loginStatus);
if(vm.$store.getters.g_loginStatus === true){
next('/');
}else{
next();
}
})
}
}
**routs.js**
import Home from './components/Home.vue'
import Users from './components/user/Users.vue'
import Statuses from './components/user/Statuses.vue'
import Post from './components/Post.vue'
import UserStatus from './components/user/UserStatus.vue'
import Signup from './components/auth/Signup.vue'
import Login from './components/auth/Login.vue'
export const routes = [
{path: '/', component: Home, name:'home'},
{path: '/users', component: Users, name:'users'},
{path: '/statuses', component: Statuses, name:'statuses'},
{path: '/current', component: UserStatus, name:'currentUser'},
{path: '/signup', component: Signup, name:'signup'},
{path: '/login', component: Login, name:'login'},
{path: '/post', component: Post}
];