habitica-self-host/website/client/src/router/index.js
Phillip Thelen 9d16ab7dba
Sign in with Apple (#11793)
* add date check

* achievements modal polishing

* refresh private-messages page when you are already on it

* add countbadge knob to change the example

* fix lint

* typos

* typos

* typos

* add toggle for achievements categories

* typo

* fix test

* fix edit avatar modal cannot be closed

* WIP(settings): subscriber page improvements

* WIP(subscriptions): more design build-out

* fix(css): disabled button styles

* fix(css): better Amazon targeting

* fix hide tooltip + align header correctly

* disable perfect scroll

* load messages on refresh event

* fix header label + conversation actions not breaking layout on hover

* WIP(g1g1): notif

* WIP(g1g1): notif cont'd

* fix(test): snowball change

* fix(event): feature NYE card

* chore(sprites): compile

* fix(bgs): include TT required field

* add gifting banner to the max height calculation

* chore(event): enable winter customizations

* WIP(gifting): partial modal implementation

* feat(gifting): select giftee modal

* fix(gifting): notification order, modal dismiss

* Begin implementing sign in with apple

# Conflicts:
#	package-lock.json
#	website/common/script/constants.js
#	website/server/libs/auth/social.js
#	website/server/models/user/schema.js

* Add apple sign in button to website

* fix lint errors

* fix config json

* fix(modals): correct some repops

* fix(gifting): style updates

* fix(buy): modal style changes

* fix(modals): also clean out "prev"

* Attempt workaround for sign in with apple on android

* temporarily log everything as error

* refactor(modals): hide in dismiss event

* fix temporary test failure

* changes to sign in with apple

* fix: first batch of layout issues for private messages + auto sizing textarea

* fix(modals): new dismiss logic

* fix(modals): new dismiss no go??

* Only use email scope

* print debugging

* .

* ..

* ...

* username second line - open profile on face-avatar/conversation name - fix textarea height

* temporarily disable apple auth and just return data for debugging

* Hopefully this works

* .....

* WIP(subscription): unsubscribed state

* .

* ..

* MAYBE THIS ACTUALLY WORKS???

* Implement apple sign in

* fix some urls

* fix urls

* fix redirect and auth

* attempt to also request name

* fix lint error

* WIP(subscription): partial subscribed

* chore(sprites): compile

* Change approach so that it actually works

* fix config error

* fix lint errors

* Fix

* fix lint error

* lint error

* WIP(subscription): finish subscribed

* refresh on sync

* new "you dont have any messages" style + changed min textarea height

* new conversationItem style / layout

* reset message unread on reload

* chore(npm): update package-locks

* fix styles / textarea height

* feat(subscription): revised sub page RC

* list optOut / chatRevoked informations for each conversation + show why its disabled

* Improve apple redirect view

* Fix apple icon on group task registration page

* WIP(adventure): prereqs

* Block / Unblock - correct disabled states - $gray-200 instead of 300/400

* canReceive not checking chatRevoked

* fix: faceAvatar / userLink open the selected conversation user

* check if the target user is blocking the logged-in user

* fix(subs): style tweaks

* fix(profiles): short circuit contributor
Attempted fix for #11830

* chore(sprites): compile

* fix(content): missing potion data

* fix(content): missing string

* WIP(drops): new modal

* fix(subs): moar style tweaks

* check if blocks is undefined

* max-height instead of height

* fix "no messages" state + canReceive on a new conversation

* WIP(adventure): analytics fixes etc

* Improve apple signin handling

* fixed conversations width (280px on max 768 width page)

* feat(adventure): random egg+potion on 2nd task

* fix(lint): noworkies

* fix(modal): correctly construct classes

* fix(tests): expectations and escape

* Fix typo

* use base url from env variables

* fix lint

* call autosize after message is sent

* fix urls

* always verify token

* throw error when social auth could not retrieve id

* Store emails correctly for apple auth

* Retrieve name when authenticating through apple

* Fix lint errors

* fix all lint errors

* fix(content): missing strings

* Revert "always verify token"

This reverts commit 8ac40c76bfa880f68fa3ce350a86ce2151b9cf95.

# Conflicts:
#	website/server/libs/auth/social.js

* Correctly load name

* remove extra changes

* remove extra logger call

* reset package and package-lock

* add back missing packages

* use name from apple

* add support for multiple apple public keys

* add some unit and integration tests

* add apple auth integration test

* tweak social signup buttons

* pixel pushing

Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
Co-authored-by: Sabe Jones <sabrecat@gmail.com>
Co-authored-by: negue <eugen.bolz@gmail.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
2020-04-08 18:44:30 +02:00

443 lines
16 KiB
JavaScript

import Vue from 'vue';
import VueRouter from 'vue-router';
import getStore from '@/store';
import * as Analytics from '@/libs/analytics';
import handleRedirect from './handleRedirect';
import ParentPage from '@/components/parentPage';
// Static Pages
const StaticWrapper = () => import(/* webpackChunkName: "entry" */'@/components/static/staticWrapper');
const HomePage = () => import(/* webpackChunkName: "entry" */'@/components/static/home');
const AppPage = () => import(/* webpackChunkName: "static" */'@/components/static/app');
const AppleRedirectPage = () => import(/* webpackChunkName: "static" */'@/components/static/appleRedirect');
const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'@/components/static/clearBrowserData');
const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'@/components/static/communityGuidelines');
const ContactPage = () => import(/* webpackChunkName: "static" */'@/components/static/contact');
const FAQPage = () => import(/* webpackChunkName: "static" */'@/components/static/faq');
const FeaturesPage = () => import(/* webpackChunkName: "static" */'@/components/static/features');
const GroupPlansPage = () => import(/* webpackChunkName: "static" */'@/components/static/groupPlans');
const MerchPage = () => import(/* webpackChunkName: "static" */'@/components/static/merch');
const NewsPage = () => import(/* webpackChunkName: "static" */'@/components/static/newStuff');
const OverviewPage = () => import(/* webpackChunkName: "static" */'@/components/static/overview');
const PressKitPage = () => import(/* webpackChunkName: "static" */'@/components/static/pressKit');
const PrivacyPage = () => import(/* webpackChunkName: "static" */'@/components/static/privacy');
const TermsPage = () => import(/* webpackChunkName: "static" */'@/components/static/terms');
const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'@/components/auth/registerLoginReset');
const Logout = () => import(/* webpackChunkName: "auth" */'@/components/auth/logout');
// User Pages
// const StatsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/stats');
// const AchievementsPage =
// () => import(/* webpackChunkName: "user" */'./components/userMenu/achievements');
const ProfilePage = () => import(/* webpackChunkName: "user" */'@/components/userMenu/profilePage');
// Settings
const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index');
const API = () => import(/* webpackChunkName: "settings" */'@/components/settings/api');
const DataExport = () => import(/* webpackChunkName: "settings" */'@/components/settings/dataExport');
const Notifications = () => import(/* webpackChunkName: "settings" */'@/components/settings/notifications');
const PromoCode = () => import(/* webpackChunkName: "settings" */'@/components/settings/promoCode');
const Site = () => import(/* webpackChunkName: "settings" */'@/components/settings/site');
const Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription');
// Hall
const HallPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/index');
const PatronsPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/patrons');
const HeroesPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/heroes');
// Except for tasks that are always loaded all the other main level
// All the main level
// components are loaded in separate webpack chunks.
// See https://webpack.js.org/guides/code-splitting-async/
// for docs
// Tasks
const UserTasks = () => import(/* webpackChunkName: "userTasks" */'@/components/tasks/user');
// Inventory
const InventoryContainer = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/index');
const ItemsPage = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/items/index');
const EquipmentPage = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/equipment/index');
const StablePage = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/stable/index');
// Guilds
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/index');
const TavernPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/tavern');
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/myGuilds');
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/discovery');
const GroupPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/group');
const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/groupPlan');
// Group Plans
const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/index');
const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/taskInformation');
const GroupPlanBilling = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/billing');
const MessagesIndex = () => import(/* webpackChunkName: "private-messages" */ '@/pages/private-messages');
// Challenges
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/index');
const MyChallenges = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/myChallenges');
const FindChallenges = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/findChallenges');
const ChallengeDetail = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/challengeDetail');
// Shops
const ShopsContainer = () => import(/* webpackChunkName: "shops" */'@/components/shops/index');
const MarketPage = () => import(/* webpackChunkName: "shops-market" */'@/components/shops/market/index');
const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'@/components/shops/quests/index');
const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'@/components/shops/seasonal/index');
const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'@/components/shops/timeTravelers/index');
const NotFoundPage = () => import(/* webpackChunkName: "not-found" */'@/components/404');
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
base: process.env.NODE_ENV === 'production' ? '/' : __dirname, // eslint-disable-line no-process-env
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 };
},
// requiresLogin is true by default, isStatic false
routes: [
{
name: 'register', path: '/register', component: RegisterLoginReset, meta: { requiresLogin: false },
},
{
name: 'login', path: '/login', component: RegisterLoginReset, meta: { requiresLogin: false },
},
{ name: 'logout', path: '/logout', component: Logout },
{
name: 'resetPassword', path: '/reset-password', component: RegisterLoginReset, meta: { requiresLogin: false },
},
{ name: 'tasks', path: '/', component: UserTasks },
{
name: 'userProfile',
path: '/profile/:userId',
component: ProfilePage,
props: true,
children: [
{ name: 'userProfilePage', path: ':startingPage', component: ProfilePage },
],
},
{
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: 'seasonal', path: 'seasonal', component: SeasonalPage },
{ name: 'time', path: 'time', component: TimeTravelersPage },
],
},
{ name: 'party', path: '/party', component: GroupPage },
{ 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,
},
],
},
{
path: '/groups',
component: GuildIndex,
children: [
{ name: 'tavern', path: 'tavern', component: TavernPage },
{
name: 'myGuilds',
path: 'myGuilds',
component: MyGuilds,
},
{
name: 'guildsDiscovery',
path: 'discovery',
component: GuildsDiscoveryPage,
},
{
name: 'guild',
path: 'guild/:groupId',
component: GroupPage,
props: true,
},
],
},
{ path: '/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,
},
],
},
{
path: '/user',
component: ParentPage,
children: [
{ name: 'stats', path: 'stats', component: ProfilePage },
{ name: 'achievements', path: 'achievements', component: ProfilePage },
{ name: 'profile', path: 'profile', component: ProfilePage },
{
name: 'settings',
path: 'settings',
component: Settings,
children: [
{
name: 'site',
path: 'site',
component: Site,
},
{
name: 'api',
path: 'api',
component: API,
},
{
name: 'dataExport',
path: 'data-export',
component: DataExport,
},
{
name: 'promoCode',
path: 'promo-code',
component: PromoCode,
},
{
name: 'subscription',
path: 'subscription',
component: Subscription,
},
{
name: 'notifications',
path: 'notifications',
component: Notifications,
},
],
},
],
},
{
path: '/static',
component: StaticWrapper,
children: [
{
name: 'app', path: 'app', component: AppPage, meta: { requiresLogin: false },
},
{
name: 'appleRedirect', path: 'apple-redirect', component: AppleRedirectPage, meta: { requiresLogin: false },
},
{
name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage, meta: { requiresLogin: false },
},
{
name: 'communityGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage, meta: { requiresLogin: false },
},
{
name: 'contact', path: 'contact', component: ContactPage, meta: { requiresLogin: false },
},
{
name: 'faq', path: 'faq', component: FAQPage, meta: { requiresLogin: false },
},
{
name: 'features', path: 'features', component: FeaturesPage, meta: { requiresLogin: false },
},
{
name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: { requiresLogin: false },
},
{
name: 'home', path: 'home', component: HomePage, meta: { requiresLogin: false },
},
{
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
},
{
name: 'merch', path: 'merch', component: MerchPage, meta: { requiresLogin: false },
},
{
name: 'news', path: 'new-stuff', component: NewsPage, meta: { requiresLogin: false },
},
{
name: 'overview', path: 'overview', component: OverviewPage, meta: { requiresLogin: false },
},
{
name: 'plans', path: 'plans', component: GroupPlansPage, meta: { requiresLogin: false },
},
{
name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: { requiresLogin: false },
},
{
name: 'privacy', path: 'privacy', component: PrivacyPage, meta: { requiresLogin: false },
},
{
name: 'terms', path: 'terms', component: TermsPage, meta: { requiresLogin: false },
},
{
name: 'notFound', path: 'not-found', component: NotFoundPage, meta: { requiresLogin: false },
},
],
},
{
path: '/hall',
component: HallPage,
children: [
{ name: 'patrons', path: 'patrons', component: PatronsPage },
{ name: 'contributors', path: 'contributors', component: HeroesPage },
],
},
// Only used to handle some redirects
// See router.beforeEach
{ path: '/redirect/:redirect', name: 'redirect' },
{ path: '*', redirect: { name: 'notFound' } },
],
});
const store = getStore();
router.beforeEach((to, from, next) => {
const { isUserLoggedIn } = store.state;
const routeRequiresLogin = to.meta.requiresLogin !== false;
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' });
}
// 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 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,
},
});
}
Analytics.track({
hitType: 'pageview',
eventCategory: 'navigation',
eventAction: 'navigate',
page: to.name || to.path,
});
if ((to.name === 'userProfile' || to.name === 'userProfilePage') && from.name !== null) {
let startingPage = 'profile';
if (to.params.startingPage !== undefined) {
startingPage = to.params.startingPage;
}
router.app.$emit('habitica:show-profile', {
userId: to.params.userId,
startingPage,
path: to.path,
});
return null;
}
if ((to.name === 'stats' || to.name === 'achievements' || to.name === 'profile') && from.name !== null) {
router.app.$emit('habitica:show-profile', {
startingPage: to.name,
path: to.path,
});
return null;
}
if (from.name === 'userProfile' || from.name === 'userProfilePage' || from.name === 'stats' || from.name === 'achievements' || from.name === 'profile') {
router.app.$root.$emit('bv::hide::modal', 'profile');
}
return next();
});
export default router;