diff --git a/src/app.vue b/src/app.vue new file mode 100644 index 0000000000..496db0ced4 --- /dev/null +++ b/src/app.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/auth.ts b/src/auth.ts index 37505b1df3..f2c575df54 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -12,8 +12,12 @@ export async function checkAuth() { if (!currentProjectKey) return false; - const response = await api.get(`/${currentProjectKey}/auth/check`); - return response.data.data.authenticated; + try { + const response = await api.get(`/${currentProjectKey}/auth/check`); + return response.data.data.authenticated; + } catch { + return false; + } } export type LoginCredentials = { diff --git a/src/components/register.ts b/src/components/register.ts index f16449ea58..fe0db97fe3 100644 --- a/src/components/register.ts +++ b/src/components/register.ts @@ -17,6 +17,7 @@ import VList, { VListItemTitle, VListGroup } from './v-list/'; +import VNotice from './v-notice/'; import VOverlay from './v-overlay/'; import VProgressLinear from './v-progress/linear/'; import VProgressCircular from './v-progress/circular/'; @@ -42,6 +43,7 @@ Vue.component('v-list-item-icon', VListItemIcon); Vue.component('v-list-item-subtitle', VListItemSubtitle); Vue.component('v-list-item-title', VListItemTitle); Vue.component('v-list-group', VListGroup); +Vue.component('v-notice', VNotice); Vue.component('v-overlay', VOverlay); Vue.component('v-progress-linear', VProgressLinear); Vue.component('v-progress-circular', VProgressCircular); diff --git a/src/components/v-button/v-button.test.ts b/src/components/v-button/v-button.test.ts index 4f1b57fdab..2a7e0a9592 100644 --- a/src/components/v-button/v-button.test.ts +++ b/src/components/v-button/v-button.test.ts @@ -33,15 +33,15 @@ describe('Button', () => { expect(component.classes()).toContain('outlined'); }); - it('Adds the block class for block buttons', () => { + it('Adds the full-width class for full-width buttons', () => { const component = mount(VButton, { localVue, propsData: { - block: true + fullWidth: true } }); - expect(component.classes()).toContain('block'); + expect(component.classes()).toContain('full-width'); }); it('Adds the rounded class for rounded buttons', () => { diff --git a/src/components/v-button/v-button.vue b/src/components/v-button/v-button.vue index 3bcdb284a0..4b879db51a 100644 --- a/src/components/v-button/v-button.vue +++ b/src/components/v-button/v-button.vue @@ -3,7 +3,10 @@ :is="component" active-class="activated" class="v-button" - :class="[sizeClass, { block, rounded, icon, outlined, loading, secondary }]" + :class="[ + sizeClass, + { 'full-width': fullWidth, rounded, icon, outlined, loading, secondary } + ]" :type="type" :disabled="disabled" :to="to" @@ -25,7 +28,7 @@ import useSizeClass, { sizeProps } from '@/compositions/size-class'; export default defineComponent({ props: { - block: { + fullWidth: { type: Boolean, default: false }, @@ -140,8 +143,8 @@ export default defineComponent({ } } - &.block { - display: block; + &.full-width { + display: flex; min-width: 100%; } diff --git a/src/main.ts b/src/main.ts index a09c5e53f5..2f18a51c63 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,10 +13,12 @@ import './modules/register'; import './layouts/register'; import './interfaces/register'; +import App from './app.vue'; + Vue.config.productionTip = false; new Vue({ - render: h => h('router-view'), + render: h => h(App), router, i18n }).$mount('#app'); diff --git a/src/modules/collections/compositions/use-navigation.test.ts b/src/modules/collections/compositions/use-navigation.test.ts index c67128bd45..7535537dc0 100644 --- a/src/modules/collections/compositions/use-navigation.test.ts +++ b/src/modules/collections/compositions/use-navigation.test.ts @@ -34,7 +34,7 @@ describe('Modules / Collections / Compositions / useNavigation', () => { navItems = useNavigation().navItems; }); - expect(navItems).toEqual([ + expect(navItems.value).toEqual([ { collection: 'test', name: 'Test', @@ -89,8 +89,8 @@ describe('Modules / Collections / Compositions / useNavigation', () => { navItems = useNavigation().navItems; }); - expect(navItems[0].name).toBe('A Test'); - expect(navItems[1].name).toBe('B Test'); - expect(navItems[2].name).toBe('C Test'); + expect(navItems.value[0].name).toBe('A Test'); + expect(navItems.value[1].name).toBe('B Test'); + expect(navItems.value[2].name).toBe('C Test'); }); }); diff --git a/src/modules/collections/compositions/use-navigation.ts b/src/modules/collections/compositions/use-navigation.ts index e1e2057b68..be93146d1d 100644 --- a/src/modules/collections/compositions/use-navigation.ts +++ b/src/modules/collections/compositions/use-navigation.ts @@ -31,5 +31,5 @@ export default function useNavigation() { }); }); - return { navItems: navItems.value }; + return { navItems: navItems }; } diff --git a/src/modules/collections/routes/browse/browse.vue b/src/modules/collections/routes/browse/browse.vue index aec5a818b2..61d0b5823e 100644 --- a/src/modules/collections/routes/browse/browse.vue +++ b/src/modules/collections/routes/browse/browse.vue @@ -29,6 +29,7 @@ import { Collection } from '@/stores/collections/types'; import CollectionsNavigation from '../../components/navigation/'; export default defineComponent({ + name: 'collections-browse', components: { CollectionsNavigation }, props: { collection: { diff --git a/src/modules/collections/routes/detail/detail.vue b/src/modules/collections/routes/detail/detail.vue index 28ce86340a..a9c0665622 100644 --- a/src/modules/collections/routes/detail/detail.vue +++ b/src/modules/collections/routes/detail/detail.vue @@ -19,6 +19,7 @@ import api from '@/api'; import CollectionsNavigation from '../../components/navigation/'; export default defineComponent({ + name: 'collections-detail', components: { CollectionsNavigation }, props: { collection: { diff --git a/src/modules/collections/routes/overview/overview.vue b/src/modules/collections/routes/overview/overview.vue index e168c3d44a..4f3ea41d12 100644 --- a/src/modules/collections/routes/overview/overview.vue +++ b/src/modules/collections/routes/overview/overview.vue @@ -24,6 +24,7 @@ import useNavigation, { NavItem } from '../../compositions/use-navigation'; import router from '@/router'; export default defineComponent({ + name: 'collections-overview', components: { CollectionsNavigation }, diff --git a/src/router.ts b/src/router.ts index a6488e5923..826c2c65e4 100644 --- a/src/router.ts +++ b/src/router.ts @@ -4,7 +4,8 @@ import { useProjectsStore } from '@/stores/projects'; import LoginRoute from '@/routes/login'; import ProjectChooserRoute from '@/routes/project-chooser'; import { checkAuth, logout } from '@/auth'; -import { hydrate } from '@/hydrate'; +import { hydrate, dehydrate } from '@/hydrate'; +import useAppStore from '@/stores/app'; export const onBeforeEnterProjectChooser: NavigationGuard = (to, from, next) => { const projectsStore = useProjectsStore(); @@ -94,13 +95,10 @@ export function replaceRoutes(routeFilter: (routes: RouteConfig[]) => RouteConfi export const onBeforeEach: NavigationGuard = async (to, from, next) => { const projectsStore = useProjectsStore(); + const appStore = useAppStore(); - // Only on first load is from.name null. On subsequent requests, from.name is undefined | string - const firstLoad = from.name === null; - - // Before we do anything, we have to make sure we're aware of the projects that exist in the - // platform. We can also use this to (async) register all the globally available modules - if (firstLoad) { + // Make sure the projects store is aware of all projects that exist + if (projectsStore.state.projects === null) { await projectsStore.getProjects(); } @@ -112,26 +110,28 @@ export const onBeforeEach: NavigationGuard = async (to, from, next) => { // Keep the projects store currentProjectKey in sync with the route if (to.params.project && projectsStore.state.currentProjectKey !== to.params.project) { + // If the store is hydrated for the current project, make sure to dehydrate it + if (appStore.state.hydrated === true) { + await dehydrate(); + } + const projectExists = await projectsStore.setCurrentProject(to.params.project); + // If the project you're trying to access doesn't exist, redirect to `/` if (to.path !== '/' && projectExists === false) { return next('/'); } } - // If you're trying to load a full URL (including) project, redirect to the login page of that - // project if you're not logged in yet. Otherwise, redirect to the - if (firstLoad) { - const loggedIn = await checkAuth(); + // The store can only be hydrated if you're an authenticated user. If the store is hydrated, we + // can safely assume you're logged in + if (appStore.state.hydrated === false) { + const authenticated = await checkAuth(); - if (loggedIn === true) { + if (authenticated === true) { await hydrate(); - } else { - if (to.meta?.public === true) { - return next(); - } else { - return next(`/${projectsStore.state.currentProjectKey}/login`); - } + } else if (to.meta?.public !== true) { + return next(`/${to.params.project}/login`); } } diff --git a/src/routes/login/components/login-form/login-form.vue b/src/routes/login/components/login-form/login-form.vue index 686953d363..58c11c18c0 100644 --- a/src/routes/login/components/login-form/login-form.vue +++ b/src/routes/login/components/login-form/login-form.vue @@ -1,7 +1,20 @@ diff --git a/src/routes/login/components/project-error/index.ts b/src/routes/login/components/project-error/index.ts new file mode 100644 index 0000000000..8ada6cf6db --- /dev/null +++ b/src/routes/login/components/project-error/index.ts @@ -0,0 +1,4 @@ +import ProjectError from './project-error.vue'; + +export { ProjectError }; +export default ProjectError; diff --git a/src/routes/login/components/project-error/project-error.vue b/src/routes/login/components/project-error/project-error.vue new file mode 100644 index 0000000000..5ef2b7b2e0 --- /dev/null +++ b/src/routes/login/components/project-error/project-error.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/routes/login/login.vue b/src/routes/login/login.vue index 9d15ee7692..1b439e544f 100644 --- a/src/routes/login/login.vue +++ b/src/routes/login/login.vue @@ -3,6 +3,11 @@

{{ $t('sign_in') }}

+