Files
chore/frontend/vue-app/src/router/index.ts
Ryan Kegel bb5330ac17
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Has been cancelled
feat: allow bypass of reset-password and verify routes for logged-in users
2026-03-05 15:46:59 -05:00

314 lines
8.2 KiB
TypeScript

import { watch } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import ChildLayout from '../layout/ChildLayout.vue'
import ParentLayout from '../layout/ParentLayout.vue'
import ChildrenListView from '../components/shared/ChildrenListView.vue'
import ChildView from '../components/child/ChildView.vue'
import ParentView from '../components/child/ParentView.vue'
import TaskSubNav from '../components/task/TaskSubNav.vue'
import ChoreView from '../components/task/ChoreView.vue'
import KindnessView from '../components/task/KindnessView.vue'
import PenaltyView from '../components/task/PenaltyView.vue'
import ChoreEditView from '@/components/task/ChoreEditView.vue'
import KindnessEditView from '@/components/task/KindnessEditView.vue'
import PenaltyEditView from '@/components/task/PenaltyEditView.vue'
import RewardView from '../components/reward/RewardView.vue'
import RewardEditView from '@/components/reward/RewardEditView.vue'
import ChildEditView from '@/components/child/ChildEditView.vue'
import ChoreAssignView from '@/components/child/ChoreAssignView.vue'
import KindnessAssignView from '@/components/child/KindnessAssignView.vue'
import PenaltyAssignView from '@/components/child/PenaltyAssignView.vue'
import RewardAssignView from '@/components/child/RewardAssignView.vue'
import NotificationView from '@/components/notification/NotificationView.vue'
import AuthLayout from '@/layout/AuthLayout.vue'
import Signup from '@/components/auth/Signup.vue'
import AuthLanding from '@/components/auth/AuthLanding.vue'
import Login from '@/components/auth/Login.vue'
import {
isUserLoggedIn,
isParentAuthenticated,
isAuthReady,
logoutParent,
enforceParentExpiry,
} from '../stores/auth'
import ParentPinSetup from '@/components/auth/ParentPinSetup.vue'
import LandingPage from '@/components/landing/LandingPage.vue'
const routes = [
{
path: '/auth',
component: AuthLayout,
children: [
{
path: '',
name: 'AuthLanding',
component: AuthLanding,
},
{
path: 'signup',
name: 'Signup',
component: Signup,
},
{
path: 'login',
name: 'Login',
component: Login,
},
{
path: 'verify',
name: 'VerifySignup',
component: () => import('@/components/auth/VerifySignup.vue'),
},
{
path: 'forgot-password',
name: 'ForgotPassword',
component: () => import('@/components/auth/ForgotPassword.vue'),
},
{
path: 'reset-password',
name: 'ResetPassword',
component: () => import('@/components/auth/ResetPassword.vue'),
},
],
},
{
path: '/child',
component: ChildLayout,
children: [
{
path: '',
name: 'ChildrenListView',
component: ChildrenListView,
},
{
path: ':id',
name: 'ChildView',
component: ChildView,
props: true,
},
],
},
{
path: '/parent',
component: ParentLayout,
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'ParentChildrenListView',
component: ChildrenListView,
},
{
path: ':id',
name: 'ParentView',
component: ParentView,
props: true,
},
{
path: 'children/create',
name: 'CreateChild',
component: ChildEditView,
},
{
path: ':id/edit',
name: 'ChildEditView',
component: ChildEditView,
props: true,
},
{
path: 'tasks',
component: TaskSubNav,
children: [
{
path: '',
name: 'TaskView',
redirect: { name: 'ChoreView' },
},
{
path: 'chores',
name: 'ChoreView',
component: ChoreView,
},
{
path: 'kindness',
name: 'KindnessView',
component: KindnessView,
},
{
path: 'penalties',
name: 'PenaltyView',
component: PenaltyView,
},
],
},
{
path: 'tasks/chores/create',
name: 'CreateChore',
component: ChoreEditView,
},
{
path: 'tasks/chores/:id/edit',
name: 'EditChore',
component: ChoreEditView,
props: true,
},
{
path: 'tasks/kindness/create',
name: 'CreateKindness',
component: KindnessEditView,
},
{
path: 'tasks/kindness/:id/edit',
name: 'EditKindness',
component: KindnessEditView,
props: true,
},
{
path: 'tasks/penalties/create',
name: 'CreatePenalty',
component: PenaltyEditView,
},
{
path: 'tasks/penalties/:id/edit',
name: 'EditPenalty',
component: PenaltyEditView,
props: true,
},
{
path: 'rewards',
name: 'RewardView',
component: RewardView,
props: false,
},
{
path: 'rewards/create',
name: 'CreateReward',
component: RewardEditView,
},
{
path: 'rewards/:id/edit',
name: 'EditReward',
component: RewardEditView,
props: true,
},
{
path: ':id/assign-chores',
name: 'ChoreAssignView',
component: ChoreAssignView,
props: true,
},
{
path: ':id/assign-kindness',
name: 'KindnessAssignView',
component: KindnessAssignView,
props: true,
},
{
path: ':id/assign-penalties',
name: 'PenaltyAssignView',
component: PenaltyAssignView,
props: true,
},
{
path: ':id/assign-rewards',
name: 'RewardAssignView',
component: RewardAssignView,
props: true,
},
{
path: 'notifications',
name: 'NotificationView',
component: NotificationView,
props: false,
},
{
path: 'profile',
name: 'UserProfile',
component: () => import('@/components/profile/UserProfile.vue'),
},
{
path: 'pin-setup',
name: 'ParentPinSetup',
component: ParentPinSetup,
meta: { requiresAuth: true, allowNoParent: true },
},
],
},
{
path: '/',
name: 'LandingPage',
component: LandingPage,
meta: { isPublic: true },
},
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior() {
return { top: 0, left: 0, behavior: 'smooth' }
},
})
// Auth guard
router.beforeEach(async (to, from, next) => {
if (!isAuthReady.value) {
await new Promise((resolve) => {
const stop = watch(isAuthReady, (ready) => {
if (ready) {
stop()
resolve(true)
}
})
})
}
// If already logged in and trying to access /auth or landing, redirect to appropriate view
// Allow reset-password and verify through even when logged in (valid use-case from profile page)
const authBypassRoutes = ['/auth/reset-password', '/auth/verify']
if (
(to.path.startsWith('/auth') || to.path === '/') &&
isUserLoggedIn.value &&
!authBypassRoutes.some((p) => to.path.startsWith(p))
) {
if (isParentAuthenticated.value) {
return next('/parent')
} else {
return next('/child')
}
}
// Always allow /auth, landing page, and /parent/pin-setup
if (to.path.startsWith('/auth') || to.path === '/' || to.name === 'ParentPinSetup') {
return next()
}
// If not logged in, redirect to landing page
if (!isUserLoggedIn.value) {
return next('/')
}
// If parent-authenticated, allow all /parent routes
// Enforce expiry first so an elapsed session is caught immediately on navigation
enforceParentExpiry()
if (isParentAuthenticated.value && to.path.startsWith('/parent')) {
return next()
}
// If not parent-authenticated, allow all /child routes
if (!isParentAuthenticated.value && to.path.startsWith('/child')) {
return next()
}
// Otherwise, redirect based on parent authentication
if (isParentAuthenticated.value) {
return next('/parent')
} else {
// Ensure parent auth is fully cleared when redirecting away from /parent
logoutParent()
return next('/child')
}
})
export default router