Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export default {
}
},
mounted () {
this.$store.dispatch('account/checkState')
useAccountAuthStore().checkState()
useProductBrokersStore().checkFlags()
},
methods: {
Expand Down
13 changes: 5 additions & 8 deletions frontend/src/api/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,21 @@ const client = axios.create({
// Common error handler
client.interceptors.response.use(function (response) {
return response
}, function (error) {
}, async function (error) {
if (/^http/.test(error.config.url)) {
// This request is to an external URL. Allow this error to pass back to the caller
return Promise.reject(error)
}

// This is an error response from our own API (or failure to reach it)
// Lazy require to avoid circular dependency:
// api/client.js → stores/account-auth.js → api/user.js → api/client.js
const { useAccountAuthStore } = require('../stores/account-auth.js')
const { useUxLoadingStore } = require('../stores/ux-loading.js')
const store = require('../store/index.js').default
// Dynamic import breaks the circular dep: client.js → account-auth.js → api/* → client.js
const { useAccountAuthStore } = await import('../stores/account-auth.js')
const { useUxLoadingStore } = await import('../stores/ux-loading.js')
if (error.code === 'ERR_NETWORK') {
// Backend failed to respond
useUxLoadingStore().setOffline(true)
} else if (error.response && error.response.status === 401 && !useUxLoadingStore().appLoader && !useAccountAuthStore().loginInflight) {
// 401 when !pending && !loginInflight means the session has expired
store.dispatch('account/logout')
useAccountAuthStore().logout()
} else if (error.response && error.response.status === 500) {
// show toast notification
Alerts.emit(error.response.data.error + ': ' + error.response.data.message, 'warning', 7500)
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/Offline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
</div>
</template>
<script>
import { useAccountAuthStore } from '@/stores/account-auth.js'

export default {
name: 'OfflineMessage',
methods: {
reload () {
this.$store.dispatch('account/checkState')
useAccountAuthStore().checkState()
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/components/auth/UpdateExpiredPassword.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import { mapState } from 'pinia'

import userApi from '../../api/user.js'
import store from '../../store/index.js'
import FormRow from '../FormRow.vue'

import { useAccountAuthStore } from '@/stores/account-auth.js'
Expand Down Expand Up @@ -89,7 +88,7 @@ export default {
return false
}
userApi.changePassword(this.input.old_password, this.input.password).then(() => {
this.$store.dispatch('account/checkState')
useAccountAuthStore().checkState()
this.$router.go()
}).catch(e => {
this.errors.password_change = 'Password change failed'
Expand All @@ -106,7 +105,7 @@ export default {
this.$refs['row-confirm'].focus()
},
logout () {
store.dispatch('account/logout')
useAccountAuthStore().logout()
}
},
mounted () {
Expand Down
6 changes: 1 addition & 5 deletions frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,20 @@ import router from './routes.js'
import Alerts from './services/alerts.js'
import { setupSentry } from './services/error-tracking.js'
import { getServiceFactory } from './services/service.factory.js'
import store from './store/index.js'
import { skipResetPlugin } from './stores/plugins/skip-reset.plugin.js'

import './index.css'
import 'highlight.js/styles/github.css'

import ForgeUIComponents from './ui-components/index.js'

store.commit('initializeStore')

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
pinia.use(skipResetPlugin)

const app = createApp(App)
.use(ForgeUIComponents)
.use(pinia)
.use(store)
.use(router)
.use(VueShepherdPlugin)

Expand All @@ -47,7 +43,7 @@ const serviceFactory = getServiceFactory()
setupSentry(app, router)

// Boot all services before mounting
serviceFactory.bootAllServices(app, store, router)
serviceFactory.bootAllServices(app, router)
.then((services) => services.bootstrap.init())
.catch((error) => {
console.error('Bootstrap initialization failed:', error)
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,15 @@ export default {
}
if (valid) {
this.loggingIn = true
this.$store.dispatch('account/login', {
useAccountAuthStore().loginWithCredentials({
username: this.input.username,
password: this.input.password
})
}
},
submitMFAToken () {
this.loggingIn = true
this.$store.dispatch('account/login', {
useAccountAuthStore().loginWithCredentials({
token: this.input.token
})
},
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/pages/TermsAndConditions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { mapState } from 'pinia'

import userApi from '../api/user.js'
import FFLayoutBox from '../layouts/Box.vue'
import store from '../store/index.js'

import { useAccountAuthStore } from '@/stores/account-auth.js'
import { useAccountSettingsStore } from '@/stores/account-settings.js'
Expand All @@ -43,15 +42,15 @@ export default {
},
methods: {
logout () {
store.dispatch('account/logout')
useAccountAuthStore().logout()
},
async acceptAction () {
const options = {}
try {
options.tcs_accepted = this.accept
this.loading = true
await userApi.updateUser(options)
this.$store.dispatch('account/checkState')
useAccountAuthStore().checkState()
} catch (error) {
console.warn(error)
} finally {
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/pages/UnverifiedEmail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { mapActions, mapState } from 'pinia'

import userApi from '../api/user.js'
import FFLayoutBox from '../layouts/Box.vue'
import store from '../store/index.js'

import { useAccountAuthStore } from '@/stores/account-auth.js'
import { useUxToursStore } from '@/stores/ux-tours.js'
Expand Down Expand Up @@ -89,7 +88,7 @@ export default {
tick()
},
logout () {
store.dispatch('account/logout')
useAccountAuthStore().logout()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/account/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ export default {
if (this.settings['user:offboarding-required']) {
window.location.href = this.settings['user:offboarding-url']
} else {
this.$store.dispatch('account/checkState')
useAccountAuthStore().checkState()
}
})
.catch(error => {
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/pages/account/VerifyPendingEmailChange.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<form v-if="!pending" class="px-4 sm:px-6 lg:px-8 mt-8 space-y-6">
<form v-if="!appLoader" class="px-4 sm:px-6 lg:px-8 mt-8 space-y-6">
<div>
<ff-button class="m-auto" @click="verify()">Click here to verify your change of email address</ff-button>
</div>
Expand All @@ -8,18 +8,20 @@

<script>

import { mapState } from 'vuex'
import { mapState } from 'pinia'

import userApi from '../../api/user.js'
import alerts from '../../services/alerts.js'

import { useUxLoadingStore } from '@/stores/ux-loading.js'

export default {
name: 'VerifyPendingEmailChange',
props: {
token: { type: String, required: true }
},
computed: {
...mapState(['pending'])
...mapState(useUxLoadingStore, ['appLoader'])
},
methods: {
async verify () {
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/pages/account/routes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { CogIcon } from '@heroicons/vue/outline'

import store from '../../store/index.js'

import AccessRequest from './AccessRequest.vue'
import AccessRequestEditor from './AccessRequestEditor.vue'
import AccountCreate from './Create.vue'
Expand All @@ -20,6 +18,8 @@ import VerifyPendingEmailChange from './VerifyPendingEmailChange.vue'

import Account from './index.vue'

import { useAccountAuthStore } from '@/stores/account-auth.js'

export default [
{
// This is the editor being authenticated. This component bounces the user
Expand Down Expand Up @@ -114,7 +114,7 @@ export default [
path: '/account/logout',
name: 'Sign out',
redirect: function () {
store.dispatch('account/logout')
useAccountAuthStore().logout()
return { path: '/' }
}
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/team/Settings/Danger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export default {
deleteTeam () {
teamApi.deleteTeam(this.team.id).then(() => {
alerts.emit('Team successfully deleted', 'confirmation')
this.$store.dispatch('account/checkState', '/')
useAccountAuthStore().checkState('/')
}).catch(err => {
alerts.emit('Problem deleting team', 'warning')
console.warn(err)
Expand Down
44 changes: 13 additions & 31 deletions frontend/src/services/bootstrap.service.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { nextTick } from 'vue'

import { useAccountAuthStore } from '@/stores/account-auth.js'
import { useAccountSettingsStore } from '@/stores/account-settings.js'
import { useContextStore } from '@/stores/context.js'

/**
* Bootstrap Service - Handles application lifecycle and readiness detection
* @class
Expand All @@ -10,11 +14,6 @@ class BootstrapService {
*/
$app

/**
* @type {import('vuex').Store} - Vuex store instance
*/
$store

/**
* @type {import('vue-router').Router} - Vue router instance
*/
Expand All @@ -26,16 +25,14 @@ class BootstrapService {
$services

/**
* @param {{app: import('vue').App, store: import('vuex').Store, router: import('vue-router').Router, services?: Object}} options - Constructor options
* @param {{app: import('vue').App, router: import('vue-router').Router, services?: Object}} options - Constructor options
*/
constructor ({
app,
store,
router,
services = {}
}) {
this.$app = app
this.$store = store
this.$router = router
this.$services = services

Expand All @@ -57,7 +54,12 @@ class BootstrapService {
*/
async init () {
return this.waitForAppMount()
.then(() => this.waitForStoreHydration())
.then(() => {
// Eagerly create account & context stores — restores persisted state from localStorage instantly
useAccountAuthStore()
useContextStore()
useAccountSettingsStore()
})
.then(() => this.checkUser())
.then(() => this.mountApp())
.then(() => this.waitForRouterReady())
Expand All @@ -76,31 +78,13 @@ class BootstrapService {
})
}

async waitForStoreHydration () {
if (this.$store.state.initialized || this.$store.state._hydrated) {
return Promise.resolve()
}

// Wait for store hydration
return new Promise((resolve) => {
const unsubscribe = this.$store.subscribe((mutation) => {
if (mutation.type === 'initializeStore' ||
mutation.type === 'HYDRATE_COMPLETE' ||
this.$store.state._hydrated) {
unsubscribe()
resolve()
}
})
})
}

async waitForRouterReady () {
await this.$router.isReady()
}

async checkUser () {
if (window.opener) {
return this.$store.dispatch('account/checkIfAuthenticated').catch(e => e)
return useAccountAuthStore().checkIfAuthenticated().catch(e => e)
}

return Promise.resolve()
Expand Down Expand Up @@ -133,19 +117,17 @@ class BootstrapService {
let BootstrapServiceInstance = null

/**
* @param {{app: import('vue').App, store: import('vuex').Store, router: import('vue-router').Router, services?: Object}} options - Constructor options
* @param {{app: import('vue').App, router: import('vue-router').Router, services?: Object}} options - Constructor options
* @returns {BootstrapService}
*/
export function createBootstrapService ({
app,
store,
router,
services = {}
} = {}) {
if (!BootstrapServiceInstance) {
BootstrapServiceInstance = new BootstrapService({
app,
store,
router,
services
})
Expand Down
Loading
Loading