Vue router, show 404 page if id is not matching - vue.js

I am not sure how to show 404 page if parameter not matching? For example, if user name is “joe”, and if someone open “/joe/categories”, categories component should be shown.
Now I want to add guard if someone try to open for example “/james/categories”, in this case I want to do redirection to show 404 component. I am not sure what should I fix? I got error "Unexpected error when starting the router: Error: Missing required param 'catchAll'"
const routes = [
{
path: '/:userName/categories',
name: 'Categories',
component: () => import(/* webpackChunkName: "home" */'#/views/Categories'),
props: true
},
{
path: '/:userName/products',
name: 'Products',
component: () => import(/* webpackChunkName: "home" */'#/views/Products'),
props: true
},
{
path: '/:userName/settings',
name: 'Settings',
component: () => import(/* webpackChunkName: "home" */'#/views/Settings'),
props: true
},
{
path: '/login',
name: 'Login',
component: () => import(/* webpackChunkName: "login" */'#/views/Login')
},
{
path: '/registration',
name: 'Registration',
component: () => import(/* webpackChunkName: "registration" */'#/views/Registration')
},
{
path: '/:catchAll(.*)',
name: '404',
component: () => import(/* webpackChunkName: "404" */'#/views/404')
}
]
router.beforeEach((to, from, next) => {
const isAuthenticated = store.getters[‘users/isAuthenticated’]
if (!isAuthenticated && to.name !== ‘Login’ && to.name !== ‘Registration’ && to.name !== ‘404’) {
next({
name: ‘Login’
})
} else if (isAuthenticated && store.getters[‘users/getUserName’] !== to.params.userName) {
next({
name: ‘404’
})
} else {
next()
}
})

I think you can do this:
const routes = [
{
path: '/:userName/categories',
name: 'Categories',
component: () => import(/* webpackChunkName: "home" */'#/views/Categories'),
props: true
},
{
path: '/:userName/products',
name: 'Products',
component: () => import(/* webpackChunkName: "home" */'#/views/Products'),
props: true
},
{
path: '/:userName/settings',
name: 'Settings',
component: () => import(/* webpackChunkName: "home" */'#/views/Settings'),
props: true
},
{
path: '/login',
name: 'Login',
component: () => import(/* webpackChunkName: "login" */'#/views/Login')
},
{
path: '/registration',
name: 'Registration',
component: () => import(/* webpackChunkName: "registration" */'#/views/Registration')
},
{
path: '*',
name: '404',
component: () => import(/* webpackChunkName: "404" */'#/views/404')
}
]
router.beforeEach((to, from, next) => {
const isAuthenticated = store.getters[‘users/isAuthenticated’]
if (!isAuthenticated && to.name !== ‘Login’ && to.name !== ‘Registration’ && to.name !== ‘404’) {
next({
name: ‘Login’
})
} else if (isAuthenticated && store.getters[‘users/getUserName’] !== to.params.userName) {
next({
name: ‘404’
})
} else {
next()
}
})

Related

Nested route: Navigate to named route passing parent params

I have a nested route like this:
{
path: '/product/:language',
name: 'LandingHome',
component: () => import(/* webpackChunkName: "LandingHome" */ '../views/landings/Base.vue'),
props:true,
children: [
{
path: '',
name: 'LandingHome',
component: () => import(/* webpackChunkName: "LandingHome" */ '../views/landings/Home.vue'),
},
{
path: 'customers',
name: 'LandingCustomers',
component: () => import(/* webpackChunkName: "LandingCustomers" */ '../views/landings/Customers.vue'),
},
]
},
How can i push route to named route "LandingCustomers" but also passing "LandingHome" :language param ?
this.$router.push({name: "LandingCustomers", params: { language: locale })
As expected language params is passed to child route, but how can push to child and also passing params language to parent on the same time?
Thanks and have a nice day!
add the language in children routes
{
path: '/product',
name: 'LandingHome',
component: () => import(/* webpackChunkName: "LandingHome" */ '../views/landings/Base.vue'),
props:true,
children: [
{
path: ':language',
name: 'LandingHome',
component: () => import(/* webpackChunkName: "LandingHome" */ '../views/landings/Home.vue'),
},
{
path: ':language/customers',
name: 'LandingCustomers',
component: () => import(/* webpackChunkName: "LandingCustomers" */ '../views/landings/Customers.vue'),
},
]
},

Quasar \ VueJS 3 infinite route looping/

I got some routes in my routes.js file
path: '/main',
component: () => import('layouts/MainLayout.vue'),
beforeEnter: (to, from, next) => {
console.log(store.authState.auth.role)
if (Number(store.authState.auth.role) === 4) {
next({ path: '/main/admin' })
} else {
next()
}
},
children: [
{
path: '',
component: () => import('pages/Main.vue'),
meta: { requiresAuth: true }
},
{
path: 'user',
component: () => import('pages/User.vue'),
meta: { requiresAuth: true }
},
{
path: 'admin',
component: () => import('pages/Admin.vue'),
meta: { requiresAuth: true }
}
]
}
and i have some code in index.js router file:
Router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth) && !store.authState.auth.state) {
next({ path: '/' })
} else {
next()
}
})
The main idea was - if user not login - redirect to root, if user login and go to /main route, depends on his role router must redirect him to specific route.
But in fact - i got infinite loop for role === 4 (example) - what i do wrong? Thank you
Your beforeEnter navigation guard is being applied to the entire /main route, and /main/admin is nested within that route. That means that the guard is being called on /main/admin itself, so any admin user who requests that page (or is redirected to it) will be redirected from that page to itself.
Based on your description, you probably want to apply your navigation guard to just the exact /main route and not its entire tree. You could then go on to redirect to next({ path: '/main/user' }) instead of simply accepting the current path with next() at the end of the guard. If that's what you want, then you don't actually need a component there at all, since the route will never be rendered. Here's how that would look:
path: '/main',
component: () => import('layouts/MainLayout.vue'),
children: [
{
path: '',
component: () => import('pages/Main.vue'), // FIXME: not needed?
meta: { requiresAuth: true },
beforeEnter: (to, from, next) => {
if (Number(store.authState.auth.role) === 4) {
next({ path: '/main/admin' })
} else {
next({ path: '/main/user' })
}
},
},
{
path: 'user',
component: () => import('pages/User.vue'),
meta: { requiresAuth: true }
},
{
path: 'admin',
component: () => import('pages/Admin.vue'),
meta: { requiresAuth: true }
}
]
}

Router.beforeEach jumps back to login page and resets vuex state value after login

So i am trying to change isSignedIn in vuex state by clicking the sign-in button, current behaviour is, after pressing the signin button, isSignedIn change from false to true for a second, during this time url jumps from http://localhost:8080/Login to http://localhost:8080/?# then http://localhost:8080/Login with the isSignedIn as false.
Below is my code, what did i do wrong?
Thanks in advance.
components/Login.vue methods:
async signIn() {
await this.$store.dispatch('storage/actionSignIn')
await this.$router.push({path: '/'})
}
}
action.js
export async function actionSignIn ({ commit }, payload) {
commit('mutationIsSignIn')
}
mutation.js
export const mutationIsSignIn = (state, payload) => {
state.isSignedIn = true
}
state.js
isSignedIn: false,
getter.js
export function getterIsSignedIn (state) {
return state.isSignedIn
}
router/index.js
export default function ({store /*, ssrContext */}) {
const Router = new VueRouter({
routes,
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE
})
Router.beforeEach((to, from, next) => {
let isSignIn = store.getters["storage/getterIsSignedIn"]
if(to.matched.some(record => record.meta.requiresAuth)) {
if (!isSignIn) {
next('/Login')
}
} else {
next()
}
})
return Router
router/routes.js
const routes = [
{
path: '/Login',
component: () => import('pages/Login.vue'),
meta: { guest: true },
},
{
path: '/',
redirect: '/Home',
component: () => import('layouts/MainLayout.vue'),
children: [
{path: '/', component: () => import('pages/Dashboard2.vue')},
{path: '/Profile', component: () => import('pages/UserProfile.vue')},
{path: '/ProjectList', component: () => import('pages/ProjectList.vue')},
{path: '/CrmUserList', component: () => import('pages/CrmUserList.vue')},
{path: '/ClientList', component: () => import('pages/ClientList')},
{path: '/SalesDataList', component: () => import('pages/SalesDataList')},
{path: '/CategoryList', component: () => import('pages/CategoryList')},
{path: '/DailyReportList', component: () => import('pages/DailyReportList')},
{path: '/Upload', component: () => import('pages/Upload.vue')}
],
meta: { requiresAuth: true }
},
{
path: '/Lock',
component: () => import('pages/LockScreen.vue'),
meta: { guest: true },
},
{
path: '/Maintenance',
component: () => import('pages/Maintenance.vue'),
meta: { guest: true },
},
{
path: '/Lock-2',
component: () => import('pages/LockScreen-2.vue'),
meta: { guest: true },
}
]

Navigating from child component to parent component in Vue

I have a parent component with several children:
// more routes here
{
path: '/organisatie',
name: 'Organisation',
meta: { breadCrumb: 'Organisatie' },
component: () => import(/* webpackChunkName: "organisations" */ '../views/Organisation.vue'),
},
{
path: '/personen',
component: () => import(/* webpackChunkName: "people" */ '../views/People.vue'),
children: [
{
path: '/',
name: 'People',
component: () => import(/* webpackChunkName: "listpeople" */ '../views/ListPeople.vue'),
meta: { breadCrumb: 'Personen' },
},
{
path: '/personen/:id',
name: 'Person',
component: () => import(/* webpackChunkName: "person" */ '../views/Person.vue'),
meta: { breadCrumb: 'Persoon' },
children: [
{
path: '/personen/:id/edit',
name: 'Edit-user',
component: () => import(/* webpackChunkName: "editperson" */ '../views/EditPerson.vue'),
meta: { breadCrumb: 'Bewerken' },
},
],
},
],
},
I can navigate to mywebsite.com/personen/id. But if I go to another page via a router-link, say 'organisatie', the URL becomes mywebsite.com/personen/organisatie instead of mywebsite.com/organisatie.
How do I prevent this from happening?
Edit: I'm using a custom component from Vue Language Router which I dynamically add my links to: <localized-link to="item.link"></localized-link>
items: [
{ link: 'organisatie'},
]
I think it's not a good idea to repeat parent path in a child path.
Here is an example for nested routes;
https://router.vuejs.org/guide/essentials/nested-routes.html
Can you try the code below? Of course you should fix router-links aswell.
// more routes here
{
path: '/organisatie',
name: 'Organisation',
meta: { breadCrumb: 'Organisatie' },
component: () => import(/* webpackChunkName: "organisations" */ '../views/Organisation.vue'),
},
{
path: '/personen',
component: () => import(/* webpackChunkName: "people" */ '../views/People.vue'),
children: [
{
path: '/',
name: 'People',
component: () => import(/* webpackChunkName: "listpeople" */ '../views/ListPeople.vue'),
meta: { breadCrumb: 'Personen' },
},
{
path: '/:id',
name: 'Person',
component: () => import(/* webpackChunkName: "person" */ '../views/Person.vue'),
meta: { breadCrumb: 'Persoon' },
children: [
{
path: '/:id/edit',
name: 'Edit-user',
component: () => import(/* webpackChunkName: "editperson" */ '../views/EditPerson.vue'),
meta: { breadCrumb: 'Bewerken' },
},
],
},
],
},

problem while using the created function in two components at the same time

In my app I have a registred area, when you login successfully a global variable "auth" will take the value loggedin, in my main vue component App.vue I load the header component which include the navbar and the router view
I call the created function in the header component to read the value of "auth" to show the login link and hide the profile,chat and logout links
I also want to use the sema method in some router views (char and profile) to prevent the user to get acces to them and push the router to the login page when the "auth" variable is not loggedin.
In that case, the App.vue have to run the created function twice, the header component reads correctly the value of "auth" while the router view does not.
Is there any solution to do that? or any alternative to prevent the access to the registred area without login ?
update
I have tried vuex and I got this error (Cannot read property 'commit' of undefined)
and the store.state.auth still have the value of false
this is my login component
<script>
/* eslint-disable */
import axios from 'axios'
import router from '../router'
import EventBus from './EventBus.vue'
export default {
data () {
return {
email: '',
password: '',
error: ''
}
},
methods: {
login () {
axios.post('users/login', {
email: this.email,
password: this.password
}).then(res => {
console.log('user exist ')
localStorage.setItem('usertoken', res.data)
this.email = ''
this.password = ''
router.push({ name: 'Profile' })
this.$store.commit('login') // sets auth to true
this.emitMethod()
}).catch(err => {
console.log(err.message)
this.error = ('User does not exist ')
this.email = ''
this.password = ''
})
},
onReset(evt) {
evt.preventDefault()
// Reset our form values
this.email = ''
this.password = ''
// Trick to reset/clear native browser form validation state
this.show = false
this.$nextTick(() => {
this.show = true
})
},
emitMethod () {
EventBus.$emit('logged-in', 'loggedin')
}
}
}
</script>
and here is main.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import AllIosIcon from 'vue-ionicons/dist/ionicons-ios.js'
import Vuex from 'vuex'
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
Vue.use(Vuex)
Vue.use(AllIosIcon)
Vue.use(VueRouter)
const store = new Vuex.Store({
state: {
auth: false
},
mutations: {
login: (state) => state.auth = true,
logout: (state) => state.auth = false
}
})
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/products',
name: 'products',
component: () => import( '../views/Products.vue')
},{
path: '/spareParts',
name: 'spareParts',
component: () => import( '../views/SpareParts.vue')
},
{
path: '/contact',
name: 'contact',
component: () => import( '../views/Contact.vue')
},
{
path: '/login',
name: 'login',
component: () => import( '../views/logIn.vue')
},
{
path: '/About',
name: 'About',
component: () => import( '../views/About.vue')
},
{
path: '/Profile',
name: 'Profile',
component: () => import( '../views/Profile.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/Chat',
name: 'Chat',
component: () => import( '../views/Chat.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/Spare1',
name: 'Spare1',
component: () => import( '../views/Spare1.vue')
},
{
path: '/spare2',
name: 'spare2',
component: () => import( '../views/spare2.vue')
},
{
path: '/spare3',
name: 'spare3',
component: () => import( '../views/spare3.vue')
},
{
path: '/spare4',
name: 'spare4',
component: () => import( '../views/spare4.vue')
},
{
path: '/spare5',
name: 'spare5',
component: () => import( '../views/spare5.vue')
},
{
path: '/spare6',
name: 'spare6',
component: () => import( '../views/spare6.vue')
},
{
path: '/spare7',
name: 'spare7',
component: () => import( '../views/spare7.vue')
},
{
path: '/spare8',
name: 'spare8',
component: () => import( '../views/spare8.vue')
},
{
path: '/spare9',
name: 'spare9',
component: () => import( '../views/spare9.vue')
},
{
path: '/spare10',
name: 'spare10',
component: () => import( '../views/spare10.vue')
},
{
path: '/spare11',
name: 'spare11',
component: () => import( '../views/spare11.vue')
},
{
path: '/spare12',
name: 'spare12',
component: () => import( '../views/spare12.vue')
},
{
path: '/spare13',
name: 'spare13',
component: () => import( '../views/spare13.vue')
},
{
path: '/spare14',
name: 'spare14',
component: () => import( '../views/spare14.vue')
},
{
path: '/spare15',
name: 'spare15',
component: () => import( '../views/spare15.vue')
},
{
path: '/spare16',
name: 'spare16',
component: () => import( '../views/spare16.vue')
},
{
path: '/spare17',
name: 'spare17',
component: () => import( '../views/spare17.vue')
},
{
path: '/spare18',
name: 'spare18',
component: () => import( '../views/spare18.vue')
},
{
path: '/spare19',
name: 'spare19',
component: () => import( '../views/spare19.vue')
},
{
path: '/spare20',
name: 'spare20',
component: () => import( '../views/spare20.vue')
},
{
path: '/spare21',
name: 'spare21',
component: () => import( '../views/spare21.vue')
},
{
path: '/spare22',
name: 'spare22',
component: () => import( '../views/spare22.vue')
},
{
path: '/spare23',
name: 'spare23',
component: () => import( '../views/spare23.vue')
},
{
path: '/product1',
name: 'product1',
component: () => import( '../views/product1.vue')
},
{
path: '/freezer',
name: 'freezer',
component: () => import( '../views/freezer.vue')
},
{
path: '/construction',
name: 'construction',
component: () => import( '../views/construction.vue')
},
{
path: '/product2',
name: 'product2',
component: () => import( '../views/product2.vue')
},
{
path: '/earth',
name: 'earth',
component: () => import( '../views/earth.vue')
},
{
path: '/crawler',
name: 'crawler',
component: () => import( '../views/crawler.vue')
},
{
path: '/articulated',
name: 'articulated',
component: () => import( '../views/articulated.vue')
},
{
path: '/wheel',
name: 'wheel',
component: () => import( '../views/wheel.vue')
},
{
path: '/tractor',
name: 'tractor',
component: () => import( '../views/tractor.vue')
},
{
path: '/telescopic',
name: 'telescopic',
component: () => import( '../views/telescopic.vue')
},
{
path: '/loader',
name: 'loader',
component: () => import( '../views/loader.vue')
},
{
path: '/pipe',
name: 'pipe',
component: () => import( '../views/pipe.vue')
},
{
path: '/pontoon',
name: 'pontoon',
component: () => import( '../views/pontoon.vue')
},
{
path: '/duty',
name: 'duty',
component: () => import( '../views/duty.vue')
},
{
path: '/attachment',
name: 'attachment',
component: () => import( '../views/attachment.vue')
},
{
path: '/customer',
name: 'customer',
component: () => import( '../views/customer.vue')
},
{
path: '/side',
name: 'side',
component: () => import( '../views/side.vue')
},
{
path: '/wine',
name: 'wine',
component: () => import( '../views/wine.vue')
},
{
path: '/accessories',
name: 'accessories',
component: () => import( '../views/accessories.vue')
},
{
path: '/hotel',
name: 'hotel',
component: () => import( '../views/hotel.vue')
},
{
path: '/bakery',
name: 'bakery',
component: () => import( '../views/bakery.vue')
},
{
path: '/retail',
name: 'retail',
component: () => import( '../views/retail.vue')
},
{
path: '/industry',
name: 'industry',
component: () => import( '../views/industry.vue')
},
{
path: '/mining',
name: 'mining',
component: () => import( '../views/mining.vue')
},
{
path: '/mobile',
name: 'mobile',
component: () => import( '../views/mobile.vue')
},
{
path: '/material',
name: 'material',
component: () => import( '../views/material.vue')
},
{
path: '/maritime',
name: 'maritime',
component: () => import( '../views/maritime.vue')
},
{
path: '/aero',
name: 'aero',
component: () => import( '../views/aero.vue')
},
{
path: '/gear',
name: 'gear',
component: () => import( '../views/gear.vue')
},
{
path: '/combust',
name: 'combust',
component: () => import( '../views/combust.vue')
},
{
path: '/hotelgroup',
name: 'hotelgroup',
component: () => import( '../views/hotelgroup.vue')
},
{
path: '/deep',
name: 'deep',
component: () => import( '../views/deep.vue')
},
{
path: '/tower',
name: 'tower',
component: () => import( '../views/tower.vue')
},
{
path: '/concrete',
name: 'concrete',
component: () => import( '../views/concrete.vue')
},
{
path: '/problem',
name: 'problem',
component: () => import( '../views/problem.vue')
}
]
const router = new VueRouter({
routes // short for routes: routes
})
router.beforeEach((to, from, next) => {
console.error(store.state.auth)
if (to.meta.requiresAuth && !store.state.auth) {
// this route requires auth, check if logged in
// if not, redirect to login page.
next({ name: 'login' })
} else {
next() // does not require auth, make sure to always call next()!
}
})
export default router; store
updated
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import AllIosIcon from 'vue-ionicons/dist/ionicons-ios.js'
import i18n from './i18n'
import store from './router'
Vue.use(AllIosIcon)
Vue.use(BootstrapVue)
Vue.config.productionTip = false
new Vue({
router,
i18n,
store,
render: h => h(App)
}).$mount('#app')
now I got this error
_this.$store.commit is not a function
In my opinion, the best way to handle this is to use vuex for state management and vue-router's navigation guards.
Look at the code below to understand how this might be done.
main.js
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
Vue.use(Vuex)
Vue.use(VueRouter)
const store = new Vuex.Store({
state: {
auth: false
},
mutations: {
login: (state) => state.auth = true,
logout: (state) => state.auth = false
}
})
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/dashboard',
component: Dashboard,
name: 'dashboard',
meta: {
requiresAuth: true
}
},
{
path: '/login',
component: Login,
name: 'login',
},
]
})
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.auth) {
next({
path: '/login',
query: {
redirectTo: to.path
}
})
}
next()
})
const app = new Vue({
el: '#app',
store,
router
})
What we are doing here is setting up a central source of truth for the auth status of a user. The beforeEach navigation guard is run before entering a route. In this case we are checking if the route requires authentication (using the requiresAuth metadata). If the route requires authentication and you're not logged in, it will redirect you to a login page.
Your login page, should have logic that logs in the user and sets the authentication state to true.
Login.vue
<template>
// ...
</template>
<script>
export default {
methods: {
login() {
// login logic here
// after successful login
this.$store.commit('login') // sets auth to true
}
}
}
</script>
This might involve more process but you end up with better results at the end of the day.