I write codes with Vuex to login and logout in my Laravel single page application it's working well but when i login to an account the profiles information (name, address, Email, ...)doesn't show in profile but after i reload the page the profile information loads, and when another user try the profile the data of the last person that login shown to him/her
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('Somthing is wrong!!')
})
})
}
export function login(credentials){
return new Promise((res,rej)=>{
axios.post('./api/auth/login', credentials)
.then(response => {
res(response.data);
})
.catch(err => {
rej('The Email or password is incorrect!')
})
})
}
export function getLoggedinUser(){
const userStr = localStorage.getItem('user');
if(!userStr){
return null
}
return JSON.parse(userStr);
}
store.js:
import {getLoggedinUser} from './partials/auth';
const user = getLoggedinUser();
export default {
state: {
currentUser: user,
isLoggedIn: !!user,
loading: false,
auth_error: null,
reg_error:null,
registeredUser: 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;
},
},
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;
},
},
actions: {
login(context){
context.commit("login");
},
}
};
general.js:
export function initialize(store, router) {
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
const currentUser = store.state.currentUser;
if(requiresAuth && !currentUser) {
next('/login');
} else if(to.path == '/login' && currentUser) {
next('/');
} else {
next();
}
if(to.path == '/register' && currentUser) {
next('/');
}
});
axios.interceptors.response.use(null, (error) => {
if (error.resposne.status == 401) {
store.commit('logout');
router.push('/login');
}
return Promise.reject(error);
});
if (store.getters.currentUser) {
setAuthorization(store.getters.currentUser.token);
}
}
export function setAuthorization(token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`
}
I think that this issue is relate to my localstorage, how can i fix this?
I'm novice at the Vue and don't have any idea what is the problem.
Login Component:
<template>
<main>
<form #submit.prevent="authenticate">
<div class="grid-x grid-padding-x">
<div class="small-10 small-offset-2 cell" v-if="registeredUser">
<p class="alert success">Welcome {{registeredUser.name}}</p>
</div>
<div class="small-10 small-offset-2 cell" v-if="authError">
<p class="alert error">
{{authError}}
</p>
</div>
<div class="small-2 cell">
<label for="email" class="text-right middle">Email:</label>
</div>
<div class="small-10 cell">
<input type="email" v-model="formLogin.email" placeholder="Email address">
</div>
<div class="small-2 cell">
<label for="password" class="text-right middle">Password:</label>
</div>
<div class="small-10 cell">
<input type="password" v-model="formLogin.password" placeholder="Enter password">
</div>
<div class="small-10 small-offset-2 cell">
<div class="gap"></div>
<input type="submit" value="Login" class="button success expanded">
</div>
</div>
</form>
</main>
</template>
<script>
import {login} from '../../partials/auth';
export default {
data(){
return {
formLogin: {
email: '',
password: ''
},
error: null
}
},
methods:{
authenticate(){
this.$store.dispatch('login');
login(this.$data.formLogin)
.then(res => {
this.$store.commit("loginSuccess", res);
this.$router.push({path: '/profile'});
})
.catch(error => {
this.$store.commit("loginFailed", {error});
})
}
},
computed:{
authError(){
return this.$store.getters.authError
},
registeredUser(){
return this.$store.getters.registeredUser
}
}
}
</script>
Localstorage data is once loaded on page load, so when you use setItem, this won't be visible until the next time.
You should store the data to vuex store, and use that as the source. Only set and get the data from localstorage on page loads.
Otherwise use something like: https://github.com/robinvdvleuten/vuex-persistedstate
I solved the problem.I have this code in my EditProfile component.
methods: {
getAuthUser () {
axios.get(`./api/auth/me`)
.then(response => {
this.user = response.data
})
},
}
this.user = response.data is wrong, I changed to this:
getAuthUser () {
this.user = this.$store.getters.currentUser
},
Related
In a Laravel 8 view, I have a Vue component with a form.
<contact-form-component contact-store-route="{{ route('contact.store') }}">
</contact-form-component>
ContactFormController.vue
<template>
<div>
<div v-if="success">
SUCCESS!
</div>
<div v-if="error">
ERROR!
</div>
<div v-show="!success">
<form
#submit.prevent="storeContact"
method="POST"
novalidate="novalidate"
#keydown="clearError"
>
<input type="text" name="fullname" v-model="formData.fullname" />
<input type="text" name="email" v-model="formData.email" />
<input type="text" name="phone" v-model="formData.phone"/>
<button type="submit">Submit</button>
</form>
</div>
</template>
<script>
import Form from "./Form.vue";
export default {
mixins: [Form],
props: {
contactStoreRoute: String,
},
data() {
return {
formData: {
fullname: null,
lname: null,
email: null,
phone: null,
message: null,
},
};
},
methods: {
storeContact() {
this.post(this.contactStoreRoute, this.formData);
},
},
mounted() {},
};
</script>
Form.vue
<template></template>
<script>
import FormErrors from "./FormErrors.vue";
export default {
name: "Form",
mixins: [FormErrors],
data() {
return {
success: false,
error: false,
errorMessage: "",
};
},
methods: {
post(url, data) {
this.success = false;
this.error = false;
axios
.post(url, data)
.then((res) => {
this.onSuccess(res.data.message);
})
.catch((error) => {
if (error.response.status == 422) {
this.setErrors(error.response.data.errors);
} else {
this.onFailure(error.response.data.message);
}
});
},
get(url, data) {
this.success = false;
this.error = false;
axios
.get(url, data)
.then((res) => {
this.onSuccess(res.data.message);
})
.catch((error) => {
if (error.response.status == 422) {
this.setErrors(error.response.data.errors);
} else {
this.onFailure(error.response.data.message);
}
});
},
onSuccess(message) {
this.reset();
this.success = true;
},
onFailure(message) {
this.error = true;
this.errorMessage = message;
},
reset() {
this.clearAllErrors();
for (let field in this.formData) {
this.formData[field] = null;
}
},
},
};
</script>
FormErrors.vue
<template></template>
<script>
export default {
name: "FormErrors",
data() {
return {
errors: {},
};
},
methods: {
setErrors(errors) {
this.errors = errors;
},
hasError(fieldName) {
return fieldName in this.errors;
},
getError(fieldName) {
return this.errors[fieldName][0];
},
clearError(event) {
Vue.delete(this.errors, event.target.name);
},
clearAllErrors() {
this.errors = {};
},
},
computed: {
hasAnyError() {
return Object.keys(this.errors).length > 0;
},
},
};
</script>
When the form is submitted, a laravel post route is called and the information is stored in the database.
Route::post('/contact/store', [ContactController::class,'store'])->name('contact.store');
After this, the Vue component now hides the form and displays a "success" message. So far, everything works great.
Now, I would like to add a step. Instead of success message, I want to obtain the last id entered in the db and show a new form with a hidden field last_id. I am unsure of how to obtain this information from the controller.
It would be a continuation of the previous form, but I do not want to gather all the data at once, I want it in steps. Now, it is also important to gather data from the first form, and if the user quits after the first form that is fine, no problem, but if the user continues with the second form I need to "link" it to the previous form through the last_id.
I think that I am not approaching this problem correctly, maybe I need to change the logic of what I am doing.
Adding last_id to ContactController return:
class ContactController extends Controller
{
public function store(Request $request)
{
$data = $request->validate([
'fullname' => 'required',
'email' => 'required|email',
'phone' => 'required',
]);
$contact_form = Contact::create($data);
$last_id = $contact_form->id;
return [
//how do I "send" this to the Vue component?
'last_id' => $last_id
];
}
}
This would be the "second step" form using the last ID. It would have it's own post route.
<h4>Your form has been successfully submitted, now please give us more info that will be linked to the previous form:</h4>
<form>
<input type="hidden" name="last_id" value="{HOW_TO_GET_THE_LAST_ID_HERE?}" />
<textarea name="message" required></textarea>
<input type="submit" value="Submit" />
</form>
You can use a JSON response:
https://laravel.com/docs/9.x/responses#json-responses
You can return the id in your Controller for example like this:
return response()
->json(['last_id' => $last_id])
You will be able to get the value from the response object of the post call
.then((res) => {
//get the id from the response
this.last_id = (res.data.last_id);
})
I'm working on a laravel 9 project that uses vue 3. For each form input has been made field components.Everything works fine except registration checkbox (Accept terms). If the checkbox is used normally in vue, it works fine. as soon as a component is made of it, it no longer works. By clicking the chackbox on and off I get the message that the accept terms is required!
I hope you can help me with this
-Vue Registration Form componetnt
<template>
<form class="text-left" method="post" #submit.prevent="submit()">
<div class="alert alert-success mb-1" v-if="success" v-html="successMessage"></div>
<div class="alert alert-danger mb-1" v-if="errors.message" v-html="errors.message"></div>
<InputFieldWithoutIcon ref="email" type="email" name="email" label="E-mail" :errors="errors" #update:field="fields.email=$event"/>
<InputFieldWithoutIcon ref="password" type="password" name="password" label="Password" :errors="errors" #update:field="fields.password=$event"/>
<InputFieldWithoutIcon ref="password_confirmation" type="password" name="password_confirmation" label="Password Confirmation" :errors="errors" #update:field="fields.password_confirmation=$event"/>
<Checkbox :text="newsletter_text" name="newsletter" type="checkbox" :errors="errors" #update:field="fields.newsletter=$event"/>
<Checkbox :text="policy_text" name="policy_accepted" type="checkbox" :errors="errors" #update:field="fields.policy_accepted=$event"/>
<SubmitButtonWithoutIcon name="create account" type="submit" custom_btn_class="btn-block btn-primary" custom_div_class=""/>
</form>
</template>
<script>
import InputFieldWithoutIcon from "../components/InputFieldWithoutIcon";
import SubmitButtonWithoutIcon from "../components/SubmitButtonWithoutIcon";
import Checkbox from "../components/Checkbox";
export default {
name: "RegistrationUser",
components: {
InputFieldWithoutIcon,
SubmitButtonWithoutIcon,
Checkbox
},
data() {
return {
fields: {
email: '',
password: '',
password_confirmation: '',
policy_accepted: '',
newsletter: '',
},
errors: {},
success: false,
successMessage: '',
newsletter_text: 'I want to receive newsletters',
policy_text: 'I accept <a class="text-bold text-underline" href="" target="_blank" title="privacy policy">the policy</a>',
}
},
methods: {
submit() {
axios.post('/api/registration', this.fields)
.then(response => {
if (response.status === 200) {
this.$refs.email.value = null;
this.$refs.password.value = null;
this.$refs.password_confirmation.value = null;
this.fields = {
email: '',
password: '',
password_confirmation: '',
policy_accepted: '',
newsletter: '',
};
this.errors = {};
this.success = true;
this.successMessage = response.data.message;
}
}).catch(errors => {
if (errors.response.status === 422 || errors.response.status === 401) {
this.errors = errors.response.data.errors;
}
});
}
}
}
</script>
-Vue Checkbox component
<template>
<div class="custom-control custom-checkbox">
<input class="custom-control-input"
:id="name"
:name="name"
:type="type"
v-model="value"
#input="updateField()"
>
<label class="custom-control-label" :for="name">
{{ text }}
</label>
<div class="invalid-feedback" v-text="errorMessage()"></div>
</div>
</template>
<script>
export default {
name: "Checkbox",
props: [
'name',
'type',
'text',
'errors'
],
data: function () {
return {
value: false
}
},
computed: {
hasError: function () {
return this.errors && this.errors[this.name] && this.errors[this.name].length > 0;
}
},
methods: {
updateField: function () {
this.clearErrors(this.name);
this.$emit('update:field', this.value)
},
errorMessage: function () {
if (this.errors && this.errors[this.name]) {
return this.errors[this.name][0];
}
},
clearErrors: function () {
if (this.hasError) {
this.errors[this.name] = null;
}
}
}
}
</script>
-Laravel Registration request
public function rules(): array
{
return [
'email' => 'required|email|unique:users',
'password' => 'required|confirmed|min:8',
'newsletter' => 'nullable|boolean',
'policy_accepted' => 'accepted'
];
}
I've found the solution.
In the checkbox template instead of using #input="updateField()" I replaced that with #change="updateField()"
That's all!
I am using Laravel 7 and Vue.js 2.
I make a delete call with axios and if everything is correct I receive as a response the new data in the related tables to update a select and if there is an error I receive the errors of the Laravel validator.
The problem is that I have to understand with javascript if the response is an error or not... but I don't know how to do that.
This is my Vue component:
<template>
<form method="DELETE" #submit.prevent="removeTask">
<div class="form-group">
<title-form v-model="titleForm" :titleMessage="titleForm"></title-form>
</div>
<hr>
<div class="form-group">
<label for="tasks">Tasks:</label>
<select required v-model="user.tasks" class="form-control" id="tasks" #mouseover="displayResults(false, false)">
<option v-for="task in tasks_user" :value="task.id" :key="task.id">
{{ task.task_name }} - {{ task.task_description }}
</option>
</select>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
<hr>
<div class="form-group">
<validated-errors :errorsForm="errors" v-if="displayErrors===true"></validated-errors>
<!--<success-alert :success_message="successMessage" v-if="displaySuccess===true"></success-alert>-->
</div>
</form>
</template>
<script>
import ValidatedErrors from "./ValidatedErrors.vue"
import SuccessAlert from "./SuccessAlert.vue"
import TitleForm from "./TitleForm.vue"
export default {
components: {
'validated-errors': ValidatedErrors,
'success-alert': SuccessAlert,
'title-form': TitleForm
},
mounted() {
console.log('Component mounted.');
},
props: {
tasks_user: {
type: Array,
required: true,
default: () => [],
}
},
computed: {
titleForm: function () {
return "Remove a task from " + this.tasks_user[0].user_name;
}
},
data: function() {
return {
user: {
tasks: ""
},
errors: {},
displayErrors: false,
displaySuccess: false,
successMessage: "The task has been removed."
}
},
methods: {
removeTask: function() {
alert(this.user.tasks);
//axios.delete('/ticketsapp/public/api/remove_task_user?id=' + this.user.tasks)
axios.delete('/ticketsapp/public/api/remove_task_user?id=' + 101)
.then((response) => {
console.log(response.data);
if(typeof response.data[0].task_id !== "undefined") {
alert("There are no errors.");
} else {
alert("There are errors.");
}
if (typeof response.data[0].task_id === "undefined") { //problem
alert('noviva');
console.log(response.data);
this.errors = response.data;
this.displayErrors = true;
} else {
alert('viva');
this.tasks_user = response.data;
this.errors = {};
}
})
.catch(error => {
alert(noooooo);
console.log(error);
});
},
displayResults(successShow, errorShow) {
this.displayErrors = errorShow;
this.displaySuccess = successShow;
}
},
}
</script>
This is my method in the controller:
public function remove(Request $request) {
$validator = Validator::make($request->all(), [
'id' => 'required|exists:task_user,id'
]);
if ($validator->fails()) {
return response($validator->errors()); //problem
}
$task_user_id = $request->id;
$user_id = TaskUser::where('id', $task_user_id)->pluck('user_id')->first();
TaskUser::find($task_user_id)->delete();
$tasks_user = TaskUser::with(['user', 'task'])->get();
$tasks_user = TaskUser::where('user_id', $user_id)->get();
$tasks_user = TaskUserResource::collection($tasks_user);
return json_encode($tasks_user);
}
To distinguish the type of return I created this condition: if (typeof response.data[0].task_id === "undefined") but when that condition is true everything falls down and I receive the following error in the console:
Uncaught (in promise) ReferenceError: noooooo is not defined
So how can I do to distinguish the type of return of the API call?
I am doing the search engine section in VueJS and Laravel, but I have a problem that does not allow me to advance in the other sections. The search engine opens and everything but when I write it only sends the first letter or 2 but not all of them like this in this image:
image of the data you send
the data that I write
After that it shows me the following error in console:
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/search?q=th"
Now showing my search engine code:
<template>
<div class="form_MCycW">
<form autocomplete="off" #sumbit.prevent>
<label class="visuallyhidden" for="search">Search</label>
<div class="field_2KO5E">
<input id="search" ref="input" v-model.trim="query" name="search" type="text" placeholder="Search for a movie, tv show or person..." #keyup="goToRoute" #blur="unFocus">
<button v-if="showButton" type="button" aria-label="Close" #click="goBack">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-miterlimit="10" stroke-width="1.5"><path d="M.75.75l13.5 13.5M14.25.75L.75 14.25"/></g></svg>
</button>
</div>
</form>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
query: this.$route.query.q ? this.$route.query.q : ''
}
},
computed: {
showButton() {
return this.$route.name === 'search';
},
...mapState({
search: state => state.event.fromPage
})
},
mounted() {
this.$refs.input.focus();
},
methods: {
goToRoute() {
if (this.query) {
this.$router.push({
name: 'search',
query: { q: this.query },
});
} else {
this.$router.push({
path: this.fromPage,
});
}
},
goBack() {
this.query = '';
this.$router.push({
path: '/',
});
},
unFocus (e) {
if (this.$route.name !== 'search') {
const target = e.relatedTarget;
if (!target || !target.classList.contains('search-toggle')) {
this.query = '';
this.$store.commit('closeSearch');
}
}
}
}
}
</script>
This is the other section of the search engine:
<template>
<main class="main">
<div class="listing">
<div class="listing__head"><h2 class="listing__title">{{ title }}</h2></div>
<div class="listing__items">
<div class="card" v-for="(item, index) in data.data" :key="index">
<router-link :to="{ name: 'show-serie', params: { id: item.id }}" class="card__link">
<div class="card__img lazyloaded"><img class="lazyload image_183rJ" :src="'/_assets/img/covers/posters/' + item.poster" :alt="item.name"></div>
<h2 class="card__name">{{ item.name }}</h2>
<div class="card__rating">
<div class="card__stars"><div :style="{width: item.rate * 10 + '%'}"></div></div>
<div class="card__vote">{{ item.rate }}</div>
</div>
</router-link>
</div>
</div>
</div>
</main>
</template>
<script>
import { mapState } from 'vuex';
let fromPage = '/';
export default {
name: "search",
metaInfo: {
bodyAttrs: {
class: 'page page-search'
}
},
computed: {
...mapState({
data: state => state.search.data,
loading: state => state.search.loading
}),
query() {
return this.$route.query.q ? this.$route.query.q : '';
},
title() {
return this.query ? `Results For: ${this.query}` : '';
},
},
async asyncData ({ query, error, redirect }) {
try {
if (query.q) {
this.$store.dispatch("GET_SEARCH_LIST", query.q);
} else {
redirect('/');
}
} catch {
error({ message: 'Page not found' });
}
},
mounted () {
this.$store.commit('openSearch');
this.$store.commit('setFromPage', fromPage);
if (this.data.length == 0 || this.data === null) {
this.$store.dispatch("GET_SEARCH_LIST", this.query);
}
setTimeout(() => {
this.showSlideUpAnimation = true;
}, 100);
},
beforeRouteEnter (to, from, next) {
fromPage = from.path;
next();
},
beforeRouteUpdate (to, from, next) {
next();
},
beforeRouteLeave (to, from, next) {
const search = document.getElementById('search');
next();
if (search && search.value.length) {
this.$store.commit('closeSearch');
}
}
};
</script>
In my routes section it is defined as follows:
{
name: 'search',
path: '/search',
component: require('../views/' + themeName + '/control/search/index').default
}
It is supposed to be a real-time search engine. I would appreciate your help in solving this problem...
What you need is a debounce. What it does is that it wait or delay till the user had finished typing before the model get updated or before you send it to the server.
An example of how it works is here
Here is a package for it.
https://github.com/vuejs-tips/v-debounce
So I have this code:
<template>
<div id="search-wrapper">
<div>
<input
id="search_input"
ref="autocomplete"
placeholder="Search"
class="search-location"
onfocus="value = ''"
#keyup.enter.native="displayPic"
>
</div>
</div>
</template>
<script>
import VueGoogleAutocomplete from "vue-google-autocomplete";
export default {
data: function() {
return {
search_input: {},
pic: {}
};
},
mounted() {
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete
);
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
let pic=place.photos[0].getUrl()
console.log(pic);
});
}
});
},
methods: {
displayPic(ref){
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete);
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
let pic=place.photos[0].getUrl()
console.log(pic);
});
}
})
},
}
}
I want to pass the "pic" parameter, resulted in displayPic, which is a function, into my template, after one of the locations is being selected.
I've tried several approaches, but I'm very new to Vue so it's a little bit tricky, at least until I'll understand how the components go.
Right now, the event is on enter, but I would like it to be triggered when a place is selected.
Any ideas how can I do that?
Right now, the most important thing is getting the pic value into my template.
<template>
<div id="search-wrapper">
<div>
<input style="width:500px;"
id="search_input"
ref="autocomplete"
placeholder="Search"
class="search-location"
onfocus="value = ''"
v-on:keyup.enter="displayPic"
#onclick="displayPic"
>
<img style="width:500px;;margin:5%;" :src="pic" >
</div>
</div>
</template>
<script>
import VueGoogleAutocomplete from "vue-google-autocomplete";
export default {
data: function() {
return {
search_input: {},
pic:""
};
},
mounted() {
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete,
{componentRestrictions: {country: "us"}}
);
},
methods: {
displayPic: function(){
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
this.pic=place.photos[0].getUrl()
});
}
})
},
}
}
</script>