Remove profileStore and access selected profile only with route.

This commit is contained in:
andrewlalis 2025-08-31 16:00:39 -04:00
parent b5b92a9af7
commit a674421337
20 changed files with 96 additions and 143 deletions

View File

@ -1,7 +1,7 @@
import { ApiClient } from './base'
import type { Currency } from './data'
import type { Page, PageRequest } from './pagination'
import type { Profile } from './profile'
import { getSelectedProfile } from './profile'
export interface AccountType {
id: string
@ -125,9 +125,9 @@ export interface AccountHistoryJournalEntryItem extends AccountHistoryItem {
export class AccountApiClient extends ApiClient {
readonly path: string
constructor(profile: Profile) {
constructor() {
super()
this.path = `/profiles/${profile.name}/accounts`
this.path = `/profiles/${getSelectedProfile()}/accounts`
}
getAccounts(): Promise<Account[]> {

View File

@ -1,3 +1,4 @@
import { useRoute, type RouteLocation } from 'vue-router'
import { ApiClient } from './base'
export interface Profile {
@ -30,3 +31,21 @@ export class ProfileApiClient extends ApiClient {
return super.getJson(`/profiles/${profileName}/properties`)
}
}
/**
* Gets the currently selected profile. Throws an error in any case where
* the route doesn't contain profile name information.
* @param route The route to get the profile from. Defaults to getting it from
* Vue's `useRoute()` which is available in component contexts.
* @returns The currently selected profile name, via the current route.
*/
export function getSelectedProfile(route: RouteLocation = useRoute()): string {
if (!('profileName' in route.params)) {
throw new Error('No "profileName" route property available.')
}
const name = route.params.profileName
if (Array.isArray(name)) {
throw new Error('"profileName" route property is an array. Expected a single string.')
}
return name
}

View File

@ -1,7 +1,7 @@
import { useProfileStore } from '@/stores/profile-store'
import { ApiClient } from './base'
import type { Currency } from './data'
import { type Page, type PageRequest } from './pagination'
import { getSelectedProfile } from './profile'
export interface TransactionVendor {
id: number
@ -146,9 +146,7 @@ export class TransactionApiClient extends ApiClient {
constructor() {
super()
const profileStore = useProfileStore()
if (!profileStore.state) throw new Error('No profile state!')
this.path = `/profiles/${profileStore.state.name}`
this.path = `/profiles/${getSelectedProfile()}`
}
getVendors(): Promise<TransactionVendor[]> {

View File

@ -6,12 +6,10 @@ import FormGroup from './form/FormGroup.vue';
import ModalWrapper from './ModalWrapper.vue';
import AppButton from './AppButton.vue';
import { AccountApiClient, AccountValueRecordType, type Account, type AccountValueRecord, type AccountValueRecordCreationPayload } from '@/api/account';
import { useProfileStore } from '@/stores/profile-store';
import { datetimeLocalToISO, getDatetimeLocalValueForNow } from '@/util/time';
import FileSelector from './FileSelector.vue';
const props = defineProps<{ account: Account }>()
const profileStore = useProfileStore()
const modal = useTemplateRef('modal')
const savedValueRecord: Ref<AccountValueRecord | undefined> = ref(undefined)
@ -33,13 +31,12 @@ async function show(): Promise<AccountValueRecord | undefined> {
}
async function addValueRecord() {
if (!profileStore.state) return
const payload: AccountValueRecordCreationPayload = {
timestamp: datetimeLocalToISO(timestamp.value),
type: AccountValueRecordType.BALANCE,
value: amount.value
}
const api = new AccountApiClient(profileStore.state)
const api = new AccountApiClient()
try {
savedValueRecord.value = await api.createValueRecord(props.account.id, payload, attachments.value)
modal.value?.close('saved')

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { getSelectedProfile } from '@/api/profile';
import type { TransactionCategory } from '@/api/transaction';
import { useProfileStore } from '@/stores/profile-store';
import { useRouter } from 'vue-router';
const router = useRouter()
@ -8,8 +8,7 @@ const props = defineProps<{ category: TransactionCategory, clickable?: boolean }
function onClicked() {
if (props.clickable) {
const profileStore = useProfileStore()
router.push(`/profiles/${profileStore.state?.name}/categories`)
router.push(`/profiles/${getSelectedProfile()}/categories`)
}
}
</script>

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { AccountApiClient, AccountHistoryItemType, type AccountHistoryItem, type AccountHistoryJournalEntryItem, type AccountHistoryValueRecordItem } from '@/api/account';
import type { PageRequest } from '@/api/pagination';
import { useProfileStore } from '@/stores/profile-store';
import { onMounted, ref, type Ref } from 'vue';
import ValueRecordHistoryItem from './ValueRecordHistoryItem.vue';
import JournalEntryHistoryItem from './JournalEntryHistoryItem.vue';
@ -10,10 +9,8 @@ const props = defineProps<{ accountId: number }>()
const historyItems: Ref<AccountHistoryItem[]> = ref([])
onMounted(async () => {
const profileStore = useProfileStore()
if (!profileStore.state) return
const pageRequest: PageRequest = { page: 1, size: 10, sorts: [{ attribute: 'timestamp', dir: 'DESC' }] }
const api = new AccountApiClient(profileStore.state)
const api = new AccountApiClient()
while (true) {
try {
const page = await api.getHistory(props.accountId, pageRequest)

View File

@ -1,15 +1,14 @@
<script setup lang="ts">
import type { AccountHistoryJournalEntryItem } from '@/api/account'
import { formatMoney } from '@/api/data';
import { useProfileStore } from '@/stores/profile-store';
import { getSelectedProfile } from '@/api/profile';
defineProps<{ item: AccountHistoryJournalEntryItem }>()
const profileStore = useProfileStore()
</script>
<template>
<div class="history-item-content">
<div>
<RouterLink :to="`/profiles/${profileStore.state?.name}/transactions/${item.transactionId}`">
<RouterLink :to="`/profiles/${getSelectedProfile()}/transactions/${item.transactionId}`">
Transaction #{{ item.transactionId }}
</RouterLink>
entered as a

View File

@ -2,17 +2,14 @@
import { AccountApiClient, type AccountHistoryValueRecordItem } from '@/api/account';
import { formatMoney } from '@/api/data';
import AppButton from '../AppButton.vue';
import { useProfileStore } from '@/stores/profile-store';
import { showConfirm } from '@/util/alert';
const props = defineProps<{ item: AccountHistoryValueRecordItem, accountId: number }>()
const profileStore = useProfileStore()
async function deleteValueRecord(id: number) {
if (!profileStore.state) return
const confirm = await showConfirm('Are you sure you want to delete this value record?')
if (!confirm) return
const api = new AccountApiClient(profileStore.state)
const api = new AccountApiClient()
try {
await api.deleteValueRecord(props.accountId, id)
} catch (err) {

View File

@ -1,12 +1,12 @@
<script setup lang="ts">
import { AccountApiClient, type Account } from '@/api/account';
import { formatMoney } from '@/api/data';
import { getSelectedProfile } from '@/api/profile';
import AddValueRecordModal from '@/components/AddValueRecordModal.vue';
import AppButton from '@/components/AppButton.vue';
import AppPage from '@/components/AppPage.vue';
import AccountHistory from '@/components/history/AccountHistory.vue';
import PropertiesTable from '@/components/PropertiesTable.vue';
import { useProfileStore } from '@/stores/profile-store';
import { showConfirm } from '@/util/alert';
import { onMounted, ref, useTemplateRef, type Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
@ -14,19 +14,13 @@ import { useRoute, useRouter } from 'vue-router';
const route = useRoute()
const router = useRouter()
const profileStore = useProfileStore()
const addValueRecordModal = useTemplateRef("addValueRecordModal")
const account: Ref<Account | null> = ref(null)
onMounted(async () => {
if (!profileStore.state) {
await router.replace('/')
return
}
const accountId = parseInt(route.params.id as string)
try {
const api = new AccountApiClient(profileStore.state)
const api = new AccountApiClient()
account.value = await api.getAccount(accountId)
} catch (err) {
console.error(err)
@ -35,12 +29,12 @@ onMounted(async () => {
})
async function deleteAccount() {
if (!profileStore.state || !account.value) return
if (!account.value) return
if (await showConfirm('Are you sure you want to delete this account? This will permanently remove the account and all associated transactions.')) {
try {
const api = new AccountApiClient(profileStore.state)
const api = new AccountApiClient()
await api.deleteAccount(account.value.id)
await router.replace(`/profiles/${profileStore.state.name}`)
await router.replace(`/profiles/${getSelectedProfile()}`)
} catch (err) {
console.error(err)
}
@ -96,8 +90,8 @@ async function addValueRecord() {
</PropertiesTable>
<div>
<AppButton @click="addValueRecord()">Record Value</AppButton>
<AppButton icon="wrench"
@click="router.push(`/profiles/${profileStore.state?.name}/accounts/${account?.id}/edit`)">Edit</AppButton>
<AppButton icon="wrench" @click="router.push(`/profiles/${getSelectedProfile()}/accounts/${account?.id}/edit`)">
Edit</AppButton>
<AppButton icon="trash" @click="deleteAccount()">Delete</AppButton>
</div>

View File

@ -58,7 +58,7 @@ async function doChangePassword() {
<template>
<AppPage title="My User">
<p>
You are logged in as <code>{{ authStore.state?.username }}</code>.
You are logged in as <code style="font-size: 14px;">{{ authStore.state?.username }}</code>.
</p>
<div style="text-align: right;">
<AppButton @click="showChangePasswordModal()">Change Password</AppButton>

View File

@ -26,7 +26,7 @@ onMounted(async () => {
}
try {
const api = new AccountApiClient(profile.value)
const api = new AccountApiClient()
accounts.value = await api.getAccounts()
} catch (err) {
console.error('Failed to load accounts', err)

View File

@ -3,12 +3,8 @@ import { ProfileApiClient, type Profile } from '@/api/profile';
import AppButton from '@/components/AppButton.vue';
import AppPage from '@/components/AppPage.vue';
import ModalWrapper from '@/components/ModalWrapper.vue';
import { useAuthStore } from '@/stores/auth-store';
import { useProfileStore } from '@/stores/profile-store';
import { onMounted, type Ref, ref, useTemplateRef } from 'vue';
import { useRouter } from 'vue-router';
const authStore = useAuthStore()
const profileStore = useProfileStore()
const router = useRouter()
const profiles: Ref<Profile[]> = ref([])
@ -16,12 +12,6 @@ const addProfileModal = useTemplateRef('addProfileModal')
const newProfileName = ref('')
onMounted(async () => {
authStore.$subscribe(async (_, state) => {
if (state.state === null) {
await router.replace('/login')
}
})
await fetchProfiles()
})
@ -35,7 +25,6 @@ async function fetchProfiles() {
}
function selectProfile(profile: Profile) {
profileStore.onProfileSelected(profile)
router.push('/profiles/' + profile.name)
}

View File

@ -1,20 +1,19 @@
<script setup lang="ts">
import { ApiError } from '@/api/base';
import { formatMoney } from '@/api/data';
import { getSelectedProfile } from '@/api/profile';
import { TransactionApiClient, type TransactionDetail } from '@/api/transaction';
import AppButton from '@/components/AppButton.vue';
import AppPage from '@/components/AppPage.vue';
import CategoryLabel from '@/components/CategoryLabel.vue';
import PropertiesTable from '@/components/PropertiesTable.vue';
import TagLabel from '@/components/TagLabel.vue';
import { useProfileStore } from '@/stores/profile-store';
import { showAlert, showConfirm } from '@/util/alert';
import { onMounted, ref, type Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute()
const router = useRouter()
const profileStore = useProfileStore()
const transaction: Ref<TransactionDetail | undefined> = ref()
@ -33,12 +32,12 @@ onMounted(async () => {
})
async function deleteTransaction() {
if (!transaction.value || !profileStore.state) return
if (!transaction.value) return
const conf = await showConfirm('Are you sure you want to delete this transaction? This will permanently delete all data pertaining to this transaction, and it cannot be recovered.')
if (!conf) return
try {
await new TransactionApiClient().deleteTransaction(transaction.value.id)
await router.replace(`/profiles/${profileStore.state.name}`)
await router.replace(`/profiles/${getSelectedProfile()}`)
} catch (err) {
console.error(err)
}
@ -120,7 +119,8 @@ async function deleteTransaction() {
</div>
<div>
<AppButton icon="wrench"
@click="router.push(`/profiles/${profileStore.state?.name}/transactions/${transaction.id}/edit`)">Edit
@click="router.push(`/profiles/${getSelectedProfile()}/transactions/${transaction.id}/edit`)">
Edit
</AppButton>
<AppButton icon="trash" @click="deleteTransaction()">Delete</AppButton>
</div>

View File

@ -1,17 +1,16 @@
<script setup lang="ts">
import { AccountApiClient, AccountTypes, type Account, type AccountType } from '@/api/account';
import { getSelectedProfile } from '@/api/profile';
import AppPage from '@/components/AppPage.vue';
import AppForm from '@/components/form/AppForm.vue';
import FormActions from '@/components/form/FormActions.vue';
import FormControl from '@/components/form/FormControl.vue';
import FormGroup from '@/components/form/FormGroup.vue';
import { useProfileStore } from '@/stores/profile-store';
import { computed, onMounted, ref, type Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute()
const router = useRouter()
const profileStore = useProfileStore()
const existingAccount: Ref<Account | null> = ref(null)
const editing = computed(() => {
@ -26,14 +25,12 @@ const currency = ref('USD')
const description = ref('')
onMounted(async () => {
if (!profileStore.state) return
const accountIdStr = route.params.id
if (accountIdStr && typeof (accountIdStr) === 'string') {
const accountId = parseInt(accountIdStr)
try {
loading.value = true
const api = new AccountApiClient(profileStore.state)
const api = new AccountApiClient()
existingAccount.value = await api.getAccount(accountId)
accountName.value = existingAccount.value.name
accountType.value = AccountTypes.of(existingAccount.value.type)
@ -49,7 +46,6 @@ onMounted(async () => {
})
async function doSubmit() {
if (!profileStore.state) return
const payload = {
name: accountName.value,
description: description.value,
@ -59,12 +55,12 @@ async function doSubmit() {
}
try {
const api = new AccountApiClient(profileStore.state)
const api = new AccountApiClient()
loading.value = true
const account = editing.value
? await api.updateAccount(existingAccount.value?.id ?? 0, payload)
: await api.createAccount(payload)
await router.replace(`/profiles/${profileStore.state.name}/accounts/${account.id}`)
await router.replace(`/profiles/${getSelectedProfile()}/accounts/${account.id}`)
} catch (err) {
console.error(err)
} finally {
@ -105,7 +101,7 @@ async function doSubmit() {
</FormControl>
</FormGroup>
<FormActions @cancel="router.replace(`/profiles/${profileStore.state?.name}`)" :disabled="loading"
<FormActions @cancel="router.replace(`/profiles/${getSelectedProfile()}`)" :disabled="loading"
:submit-text="editing ? 'Save' : 'Add'" />
</AppForm>
</AppPage>

View File

@ -12,6 +12,7 @@ The form consists of a few main sections:
<script setup lang="ts">
import { AccountApiClient, type Account } from '@/api/account';
import { DataApiClient, type Currency } from '@/api/data';
import { getSelectedProfile } from '@/api/profile';
import { TransactionApiClient, type AddTransactionPayload, type TransactionDetail, type TransactionDetailLineItem, type TransactionVendor } from '@/api/transaction';
import AppPage from '@/components/AppPage.vue';
import CategorySelect from '@/components/CategorySelect.vue';
@ -22,14 +23,12 @@ import FormControl from '@/components/form/FormControl.vue';
import FormGroup from '@/components/form/FormGroup.vue';
import LineItemsEditor from '@/components/LineItemsEditor.vue';
import TagLabel from '@/components/TagLabel.vue';
import { useProfileStore } from '@/stores/profile-store';
import { getDatetimeLocalValueForNow } from '@/util/time';
import { computed, onMounted, ref, watch, type Ref } from 'vue';
import { useRoute, useRouter, } from 'vue-router';
const route = useRoute()
const router = useRouter()
const profileStore = useProfileStore()
const existingTransaction: Ref<TransactionDetail | null> = ref(null)
const editing = computed(() => {
@ -80,10 +79,9 @@ watch(availableCurrencies, (newValue: Currency[]) => {
})
onMounted(async () => {
if (!profileStore.state) return
const dataClient = new DataApiClient()
const transactionClient = new TransactionApiClient()
const accountClient = new AccountApiClient(profileStore.state)
const accountClient = new AccountApiClient()
// Fetch various collections of data needed for different user choices.
dataClient.getCurrencies().then(currencies => allCurrencies.value = currencies)
@ -117,8 +115,6 @@ onMounted(async () => {
* created.
*/
async function doSubmit() {
if (!profileStore.state) return
const localDate = new Date(timestamp.value)
const scaledAmount = amount.value * Math.pow(10, currency.value?.fractionalDigits ?? 0)
const payload: AddTransactionPayload = {
@ -146,7 +142,7 @@ async function doSubmit() {
} else {
savedTransaction = await transactionApi.addTransaction(payload, attachmentsToUpload.value)
}
await router.replace(`/profiles/${profileStore.state.name}/transactions/${savedTransaction.id}`)
await router.replace(`/profiles/${getSelectedProfile()}/transactions/${savedTransaction.id}`)
} catch (err) {
console.error(err)
} finally {
@ -159,11 +155,10 @@ async function doSubmit() {
* profile's homepage.
*/
function doCancel() {
if (!profileStore.state) return
if (editing.value) {
router.replace(`/profiles/${profileStore.state.name}/transactions/${existingTransaction.value?.id}`)
router.replace(`/profiles/${getSelectedProfile()}/transactions/${existingTransaction.value?.id}`)
} else {
router.replace(`/profiles/${profileStore.state.name}`)
router.replace(`/profiles/${getSelectedProfile()}`)
}
}

View File

@ -1,24 +1,18 @@
<script setup lang="ts">
import { AccountApiClient, type Account } from '@/api/account'
import { formatMoney } from '@/api/data'
import { getSelectedProfile } from '@/api/profile'
import AppButton from '@/components/AppButton.vue'
import HomeModule from '@/components/HomeModule.vue'
import { useProfileStore } from '@/stores/profile-store'
import { onMounted, ref, type Ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const profileStore = useProfileStore()
const accounts: Ref<Account[]> = ref([])
onMounted(async () => {
if (!profileStore.state) {
console.warn('No profile is selected.')
return
}
const accountApi = new AccountApiClient(profileStore.state)
const accountApi = new AccountApiClient()
accountApi.getAccounts().then(result => accounts.value = result)
.catch(err => console.error(err))
})
@ -39,7 +33,7 @@ onMounted(async () => {
<tbody>
<tr v-for="account in accounts" :key="account.id">
<td>
<RouterLink :to="`/profiles/${profileStore.state?.name}/accounts/${account.id}`">{{ account.name }}
<RouterLink :to="`/profiles/${getSelectedProfile()}/accounts/${account.id}`">{{ account.name }}
</RouterLink>
</td>
<td>{{ account.currency.code }}</td>
@ -54,7 +48,7 @@ onMounted(async () => {
</table>
</template>
<template v-slot:actions>
<AppButton icon="plus" @click="router.push(`/profiles/${profileStore.state?.name}/add-account`)">Add Account
<AppButton icon="plus" @click="router.push(`/profiles/${getSelectedProfile()}/add-account`)">Add Account
</AppButton>
</template>
</HomeModule>

View File

@ -1,21 +1,33 @@
<script setup lang="ts">
import { ProfileApiClient } from '@/api/profile';
import { getSelectedProfile, ProfileApiClient, type Profile } from '@/api/profile';
import AppButton from '@/components/AppButton.vue';
import ConfirmModal from '@/components/ConfirmModal.vue';
import HomeModule from '@/components/HomeModule.vue';
import { useProfileStore } from '@/stores/profile-store';
import { useTemplateRef } from 'vue';
import { showAlert } from '@/util/alert';
import { onMounted, ref, useTemplateRef, type Ref } from 'vue';
import { useRouter } from 'vue-router';
const profileStore = useProfileStore()
const router = useRouter()
const confirmDeleteModal = useTemplateRef('confirmDeleteModal')
const profile: Ref<Profile | undefined> = ref()
onMounted(async () => {
try {
profile.value = await new ProfileApiClient().getProfile(getSelectedProfile())
} catch (err) {
console.error(err)
await showAlert("Failed to get profile.")
await router.replace("/profiles")
}
})
async function deleteProfile() {
if (profileStore.state && await confirmDeleteModal.value?.confirm()) {
const currentProfileName = getSelectedProfile()
if (await confirmDeleteModal.value?.confirm()) {
const api = new ProfileApiClient()
try {
await api.deleteProfile(profileStore.state.name)
await api.deleteProfile(currentProfileName)
await router.replace('/profiles')
} catch (err) {
console.error(err)
@ -24,14 +36,14 @@ async function deleteProfile() {
}
</script>
<template>
<HomeModule title="Profile">
<HomeModule title="Profile" v-if="profile">
<template v-slot:default>
<p>Your currently selected profile is: {{ profileStore.state?.name }}</p>
<p>Your currently selected profile is: {{ profile.name }}</p>
<p>
<RouterLink :to="`/profiles/${profileStore.state?.name}/vendors`">View all vendors here.</RouterLink>
<RouterLink :to="`/profiles/${profile.name}/vendors`">View all vendors here.</RouterLink>
</p>
<p>
<RouterLink :to="`/profiles/${profileStore.state?.name}/categories`">View all categories here.</RouterLink>
<RouterLink :to="`/profiles/${profile.name}/categories`">View all categories here.</RouterLink>
</p>
<ConfirmModal ref="confirmDeleteModal">

View File

@ -1,16 +1,15 @@
<script setup lang="ts">
import { formatMoney } from '@/api/data';
import type { Page, PageRequest } from '@/api/pagination';
import { getSelectedProfile } from '@/api/profile';
import { TransactionApiClient, type TransactionsListItem } from '@/api/transaction';
import AppButton from '@/components/AppButton.vue';
import HomeModule from '@/components/HomeModule.vue';
import PaginationControls from '@/components/PaginationControls.vue';
import { useProfileStore } from '@/stores/profile-store';
import { onMounted, ref, type Ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter()
const profileStore = useProfileStore()
const transactions: Ref<Page<TransactionsListItem>> = ref({ items: [], pageRequest: { page: 1, size: 10, sorts: [] }, totalElements: 0, totalPages: 0, isFirst: true, isLast: true })
onMounted(async () => {
@ -51,18 +50,18 @@ async function fetchPage(pageRequest: PageRequest) {
<td>{{ tx.description }}</td>
<td>
<RouterLink v-if="tx.creditedAccount"
:to="`/profiles/${profileStore.state?.name}/accounts/${tx.creditedAccount.id}`">
:to="`/profiles/${getSelectedProfile()}/accounts/${tx.creditedAccount.id}`">
{{ tx.creditedAccount?.name }}
</RouterLink>
</td>
<td>
<RouterLink v-if="tx.debitedAccount"
:to="`/profiles/${profileStore.state?.name}/accounts/${tx.debitedAccount.id}`">
:to="`/profiles/${getSelectedProfile()}/accounts/${tx.debitedAccount.id}`">
{{ tx.debitedAccount?.name }}
</RouterLink>
</td>
<td>
<RouterLink :to="`/profiles/${profileStore.state?.name}/transactions/${tx.id}`">View</RouterLink>
<RouterLink :to="`/profiles/${getSelectedProfile()}/transactions/${tx.id}`">View</RouterLink>
</td>
</tr>
</tbody>
@ -70,7 +69,7 @@ async function fetchPage(pageRequest: PageRequest) {
<PaginationControls :page="transactions" @update="pr => fetchPage(pr)"></PaginationControls>
</template>
<template v-slot:actions>
<AppButton icon="plus" @click="router.push(`/profiles/${profileStore.state?.name}/add-transaction`)">Add
<AppButton icon="plus" @click="router.push(`/profiles/${getSelectedProfile()}/add-transaction`)">Add
Transaction</AppButton>
</template>
</HomeModule>

View File

@ -1,6 +1,6 @@
import { getSelectedProfile } from '@/api/profile'
import { useAuthStore } from '@/stores/auth-store'
import { createRouter, createWebHistory, type RouteLocationNormalized } from 'vue-router'
import { useProfileStore } from '@/stores/profile-store'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -30,13 +30,12 @@ const router = createRouter({
meta: { title: 'Profiles' },
},
{
path: 'profiles/:name',
beforeEnter: profileSelected,
path: 'profiles/:profileName',
children: [
{
path: '',
component: () => import('@/pages/UserHomePage.vue'),
meta: { title: (to: RouteLocationNormalized) => 'Profile ' + to.params.name },
meta: { title: (to: RouteLocationNormalized) => 'Profile ' + getSelectedProfile(to) },
},
{
path: 'accounts/:id',
@ -85,6 +84,7 @@ const router = createRouter({
],
})
// Adds a webpage title to each route based on the "meta.title" attribute.
router.beforeEach((to, _, next) => {
if (to.meta.title !== undefined && typeof to.meta.title === 'string') {
document.title = 'Finnow - ' + to.meta.title
@ -99,6 +99,12 @@ router.beforeEach((to, _, next) => {
next()
})
/**
* Guard to ensure a route can only be accessed by authenticated users.
* @param to The route to guard.
* @returns True if the user is authenticated, or a redirect to the `/login`
* page if the user isn't logged in.
*/
function onlyAuthenticated(to: RouteLocationNormalized) {
const authStore = useAuthStore()
if (authStore.state) return true
@ -106,10 +112,4 @@ function onlyAuthenticated(to: RouteLocationNormalized) {
return '/login?next=' + encodeURIComponent(to.path)
}
function profileSelected() {
const profileStore = useProfileStore()
if (profileStore.state) return true
return '/profiles' // Send the user to /profiles to select one before continuing.
}
export default router

View File

@ -1,32 +0,0 @@
import type { Profile } from '@/api/profile'
import { defineStore } from 'pinia'
import { ref, type Ref } from 'vue'
const LOCAL_STORAGE_KEY = 'profile'
export const useProfileStore = defineStore('profile', () => {
const state: Ref<Profile | null> = ref(getStateFromLocalStorage())
function onProfileSelected(profile: Profile) {
state.value = profile
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(profile))
}
function onProfileSelectionCleared() {
state.value = null
localStorage.removeItem(LOCAL_STORAGE_KEY)
}
return { state, onProfileSelected, onProfileSelectionCleared }
})
function getStateFromLocalStorage(): Profile | null {
const jsonText = localStorage.getItem(LOCAL_STORAGE_KEY)
if (jsonText === null || jsonText.length === 0) {
localStorage.removeItem(LOCAL_STORAGE_KEY)
return null
}
const profile = JSON.parse(jsonText) as Profile
console.info('Loaded profile', profile)
return profile
}