Files
habitica/website/client/src/router/index.js
T
2026-04-08 15:35:47 -05:00

427 lines
13 KiB
JavaScript

import Vue from 'vue';
import VueRouter from 'vue-router';
import getStore from '@/store';
import handleRedirect from './handleRedirect';
import { PAGES } from '@/libs/consts';
import { STATIC_ROUTES } from './static-routes';
import { USER_ROUTES } from './user-routes';
import { DEPRECATED_ROUTES } from '@/router/deprecated-routes';
// NOTE: when adding a page make sure to implement the `common:setTitle` action
const Logout = () => import('@/components/auth/logout');
// Hall
const HallPage = () => import('@/components/hall/index');
const PatronsPage = () => import('@/components/hall/patrons');
const HeroesPage = () => import('@/components/hall/heroes');
// Admin Pages
const AdminContainerPage = () => import('@/components/admin/container');
const AdminPanelPage = () => import('@/components/admin/admin-panel');
const AdminPanelUserPage = () => import('@/components/admin/admin-panel/user-support');
const AdminPanelSearchPage = () => import('@/components/admin/admin-panel/search');
const GroupAdminPage = () => import('@/components/admin/groups');
const GroupAdminGroupPage = () => import('@/components/admin/groups/group-support');
const BlockerPage = () => import('@/components/admin/blocker');
// Tasks
const UserTasks = () => import('@/components/tasks/user');
// Inventory
const InventoryContainer = () => import('@/components/inventory/index');
const ItemsPage = () => import('@/components/inventory/items/index');
const EquipmentPage = () => import('@/components/inventory/equipment/index');
const StablePage = () => import('@/components/inventory/stable/index');
// Guilds & Parties
const GroupPage = () => import('@/components/groups/group');
const GroupPlansAppPage = () => import('@/components/static/groupPlans');
const LookingForParty = () => import('@/components/groups/lookingForParty');
// Group Plans
const GroupPlanIndex = () => import('@/components/group-plans/index');
const GroupPlanTaskInformation = () => import('@/components/group-plans/taskInformation');
const GroupPlanBilling = () => import('@/components/group-plans/billing');
const MessagesIndex = () => import('@/pages/private-messages/index.vue');
// Challenges
const ChallengeIndex = () => import('@/components/challenges/index');
const MyChallenges = () => import('@/components/challenges/myChallenges');
const FindChallenges = () => import('@/components/challenges/findChallenges');
const ChallengeDetail = () => import('@/components/challenges/challengeDetail');
// Shops
const ShopsContainer = () => import('@/components/shops/index');
const MarketPage = () => import('@/components/shops/market/index');
const QuestsPage = () => import('@/components/shops/quests/index');
const CustomizationsPage = () => import('@/components/shops/customizations/index');
const SeasonalPage = () => import('@/components/shops/seasonal/index');
const TimeTravelersPage = () => import('@/components/shops/timeTravelers/index');
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
base: '/',
linkActiveClass: 'active',
// When navigating to another route always scroll to the top
// To customize the behavior see https://router.vuejs.org/en/advanced/scroll-behavior.html
scrollBehavior () {
return { x: 0, y: 0 };
},
// meta defaults: requiresLogin true, privilegeNeeded empty
// NOTE: when adding a new route entry make sure to implement the `common:setTitle` action
// in the route component to set a specific subtitle for the page.
routes: [
{ name: 'logout', path: '/logout', component: Logout },
{ name: 'tasks', path: '/', component: UserTasks },
{
name: 'userProfile',
path: '/profile/:userId',
props: true,
},
{ name: 'profile', path: '/user/profile' },
{
name: 'avatar',
path: '/avatar',
children: [
{ name: 'backgrounds', path: 'backgrounds' },
],
},
{ name: 'stats', path: '/user/stats' },
{ name: 'achievements', path: '/user/achievements' },
{
path: '/inventory',
component: InventoryContainer,
children: [
{ name: 'items', path: 'items', component: ItemsPage },
{ name: 'equipment', path: 'equipment', component: EquipmentPage },
{ name: 'stable', path: 'stable', component: StablePage },
],
},
{
path: '/shops',
component: ShopsContainer,
children: [
{ name: 'market', path: 'market', component: MarketPage },
{ name: 'quests', path: 'quests', component: QuestsPage },
{ name: 'customizations', path: 'customizations', component: CustomizationsPage },
{ name: 'seasonal', path: 'seasonal', component: SeasonalPage },
{ name: 'time', path: 'time', component: TimeTravelersPage },
],
},
{ name: 'party', path: '/party', component: GroupPage },
{ name: 'lookingForParty', path: '/looking-for-party', component: LookingForParty },
{ name: 'groupPlan', path: '/group-plans', component: GroupPlansAppPage },
{
name: 'groupPlanDetail',
path: '/group-plans/:groupId',
component: GroupPlanIndex,
props: true,
children: [
{
name: 'groupPlanDetailTaskInformation',
path: '/group-plans/:groupId/task-information',
component: GroupPlanTaskInformation,
props: true,
},
{
name: 'groupPlanDetailInformation',
path: '/group-plans/:groupId/information',
component: GroupPage,
props: true,
},
{
name: 'groupPlanBilling',
path: '/group-plans/:groupId/billing',
component: GroupPlanBilling,
props: true,
},
],
},
DEPRECATED_ROUTES,
{ path: PAGES.PRIVATE_MESSAGES, name: 'privateMessages', component: MessagesIndex },
{
name: 'challenges',
path: '/challenges',
component: ChallengeIndex,
children: [
{
name: 'myChallenges',
path: 'myChallenges',
component: MyChallenges,
},
{
name: 'findChallenges',
path: 'findChallenges',
component: FindChallenges,
},
{
name: 'challenge',
path: ':challengeId',
component: ChallengeDetail,
props: true,
},
],
},
USER_ROUTES,
STATIC_ROUTES,
{
path: '/hall',
component: HallPage,
children: [
{ name: 'patrons', path: 'patrons', component: PatronsPage },
{ name: 'contributors', path: 'contributors', component: HeroesPage },
],
},
{
name: 'adminSection',
path: '/admin',
component: AdminContainerPage,
meta: {
privilegeNeeded: [ // any one of these is enough to give access
'userSupport',
'accessControl',
],
},
children: [
{
name: 'adminPanel',
path: 'panel',
component: AdminPanelPage,
meta: {
privilegeNeeded: [ // any one of these is enough to give access
'userSupport',
],
},
children: [
{
name: 'adminPanelSearch',
path: 'search/:userIdentifier',
component: AdminPanelSearchPage,
meta: {
privilegeNeeded: [
'userSupport',
],
},
},
{
name: 'adminPanelUser',
path: ':userIdentifier',
component: AdminPanelUserPage,
meta: {
privilegeNeeded: [
'userSupport',
],
},
},
],
},
{
name: 'groupAdmin',
path: 'groups',
component: GroupAdminPage,
meta: {
privilegeNeeded: [ // any one of these is enough to give access
'groupSupport',
],
},
children: [
{
name: 'groupAdminGroup',
path: ':groupId',
component: GroupAdminGroupPage,
meta: {
privilegeNeeded: [
'groupsSupport',
],
},
},
],
},
{
name: 'blockers',
path: 'blockers',
component: BlockerPage,
meta: {
privilegeNeeded: [ // any one of these is enough to give access
'accessControl',
],
},
},
],
},
// Only used to handle some redirects
// See router.beforeEach
{ path: '/static/tavern-and-guilds', redirect: '/static/faq/tavern-and-guilds' },
{ path: '/redirect/:redirect', name: 'redirect' },
{ path: '*', redirect: { name: 'notFound' } },
],
});
const store = getStore();
router.beforeEach(async (to, from, next) => {
const { isUserLoggedIn, isUserLoaded } = store.state;
const routeRequiresLogin = to.meta.requiresLogin !== false;
const routePrivilegeNeeded = to.meta.privilegeNeeded;
if (to.name === 'redirect') return handleRedirect(to, from, next);
if (!isUserLoggedIn && routeRequiresLogin) {
// Redirect to the login page unless the user is trying to reach the
// root of the website, in which case show the home page.
// Pass the requested page as a query parameter to redirect later.
const redirectTo = to.path === '/' ? 'home' : 'login';
return next({
name: redirectTo,
query: redirectTo === 'login' ? {
redirectTo: to.path,
} : to.query,
});
}
// Keep the redirectTo query param when going from login to register
// !to.query.redirectTo is to avoid entering a loop of infinite redirects
if (to.name === 'register' && !to.query.redirectTo && from.name === 'login' && from.query.redirectTo) {
return next({
name: 'register',
query: {
redirectTo: from.query.redirectTo,
},
});
}
if (isUserLoggedIn && (to.name === 'login' || to.name === 'register')) {
return next({ name: 'tasks' });
}
if (routePrivilegeNeeded) {
// Redirect non-admin users when trying to access a page.
if (!isUserLoaded) await store.dispatch('user:fetch');
if (!store.state.user.data.permissions.fullAccess) {
const userHasPriv = routePrivilegeNeeded.some(
privName => store.state.user.data.permissions[privName],
);
if (!userHasPriv) return next({ name: 'tasks' });
}
}
if (to.name === 'party') {
router.app.$root.$emit('update-party');
}
// Redirect old guild urls
if (to.hash.indexOf('#/options/groups/guilds/') !== -1) {
const splits = to.hash.split('/');
const guildId = splits[4];
return next({
name: 'guild',
params: {
groupId: guildId,
},
});
}
// Redirect from Guild link to Group Plan where possible
if (to.name === 'guild') {
await store.dispatch('guilds:getGroupPlans');
const { groupPlans } = store.state;
const groupPlanIds = groupPlans.data.map(groupPlan => groupPlan._id);
if (groupPlanIds.indexOf(to.params.groupId) !== -1) {
return next({
name: 'groupPlanDetailInformation',
params: {
groupId: to.params.groupId,
},
});
}
}
// Redirect old challenge urls
if (to.hash.indexOf('#/options/groups/challenges/') !== -1) {
const splits = to.hash.split('/');
const challengeId = splits[4];
return next({
name: 'challenge',
params: {
challengeId,
},
});
}
if ((to.name === 'userProfile')) {
let startingPage = 'profile';
if (to.params.startingPage !== undefined) {
startingPage = to.params.startingPage;
}
// Check if there's a hash in the URL for stats or achievements
if (to.hash === '#stats' || to.hash === '#achievements') {
startingPage = to.hash.substring(1);
}
if (from.name === null) {
store.state.postLoadModal = `profile/${to.params.userId}`;
return next({ name: 'tasks' });
}
router.app.$emit('habitica:show-profile', {
userId: to.params.userId,
startingPage,
fromPath: from.path,
toPath: to.path,
});
return null;
}
if (to.name === 'tasks' && to.query.openGemsModal === 'true') {
store.state.postLoadModal = 'buy-gems';
return next({ name: 'tasks' });
}
if ((to.name === 'stats' || to.name === 'achievements' || to.name === 'profile') && from.name !== null) {
const userId = store.state.user.data._id;
let redirectPath = `/profile/${userId}`;
if (to.name === 'stats') {
redirectPath += '#stats';
} else if (to.name === 'achievements') {
redirectPath += '#achievements';
}
router.app.$emit('habitica:show-profile', {
userId,
startingPage: to.name,
fromPath: from.path,
toPath: redirectPath,
});
return null;
}
if (from.name === 'userProfile' || from.name === 'stats' || from.name === 'achievements' || from.name === 'profile') {
router.app.$root.$emit('bv::hide::modal', 'profile');
}
if (to.name === 'backgrounds') {
store.state.avatarEditorOptions.editingUser = true;
store.state.avatarEditorOptions.startingPage = 'backgrounds';
router.app.$root.$emit('bv::show::modal', 'avatar-modal');
return null;
}
return next();
});
router.afterEach((to, from) => {
if (from.name === 'chatSunsetFaq') {
document.body.style.background = '#f9f9f9';
}
});
export default router;