Remove profileStore and access selected profile only with route.
This commit is contained in:
parent
b5b92a9af7
commit
a674421337
|
|
@ -1,7 +1,7 @@
|
||||||
import { ApiClient } from './base'
|
import { ApiClient } from './base'
|
||||||
import type { Currency } from './data'
|
import type { Currency } from './data'
|
||||||
import type { Page, PageRequest } from './pagination'
|
import type { Page, PageRequest } from './pagination'
|
||||||
import type { Profile } from './profile'
|
import { getSelectedProfile } from './profile'
|
||||||
|
|
||||||
export interface AccountType {
|
export interface AccountType {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -125,9 +125,9 @@ export interface AccountHistoryJournalEntryItem extends AccountHistoryItem {
|
||||||
export class AccountApiClient extends ApiClient {
|
export class AccountApiClient extends ApiClient {
|
||||||
readonly path: string
|
readonly path: string
|
||||||
|
|
||||||
constructor(profile: Profile) {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.path = `/profiles/${profile.name}/accounts`
|
this.path = `/profiles/${getSelectedProfile()}/accounts`
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccounts(): Promise<Account[]> {
|
getAccounts(): Promise<Account[]> {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useRoute, type RouteLocation } from 'vue-router'
|
||||||
import { ApiClient } from './base'
|
import { ApiClient } from './base'
|
||||||
|
|
||||||
export interface Profile {
|
export interface Profile {
|
||||||
|
|
@ -30,3 +31,21 @@ export class ProfileApiClient extends ApiClient {
|
||||||
return super.getJson(`/profiles/${profileName}/properties`)
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useProfileStore } from '@/stores/profile-store'
|
|
||||||
import { ApiClient } from './base'
|
import { ApiClient } from './base'
|
||||||
import type { Currency } from './data'
|
import type { Currency } from './data'
|
||||||
import { type Page, type PageRequest } from './pagination'
|
import { type Page, type PageRequest } from './pagination'
|
||||||
|
import { getSelectedProfile } from './profile'
|
||||||
|
|
||||||
export interface TransactionVendor {
|
export interface TransactionVendor {
|
||||||
id: number
|
id: number
|
||||||
|
|
@ -146,9 +146,7 @@ export class TransactionApiClient extends ApiClient {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
const profileStore = useProfileStore()
|
this.path = `/profiles/${getSelectedProfile()}`
|
||||||
if (!profileStore.state) throw new Error('No profile state!')
|
|
||||||
this.path = `/profiles/${profileStore.state.name}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getVendors(): Promise<TransactionVendor[]> {
|
getVendors(): Promise<TransactionVendor[]> {
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@ import FormGroup from './form/FormGroup.vue';
|
||||||
import ModalWrapper from './ModalWrapper.vue';
|
import ModalWrapper from './ModalWrapper.vue';
|
||||||
import AppButton from './AppButton.vue';
|
import AppButton from './AppButton.vue';
|
||||||
import { AccountApiClient, AccountValueRecordType, type Account, type AccountValueRecord, type AccountValueRecordCreationPayload } from '@/api/account';
|
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 { datetimeLocalToISO, getDatetimeLocalValueForNow } from '@/util/time';
|
||||||
import FileSelector from './FileSelector.vue';
|
import FileSelector from './FileSelector.vue';
|
||||||
|
|
||||||
const props = defineProps<{ account: Account }>()
|
const props = defineProps<{ account: Account }>()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const modal = useTemplateRef('modal')
|
const modal = useTemplateRef('modal')
|
||||||
const savedValueRecord: Ref<AccountValueRecord | undefined> = ref(undefined)
|
const savedValueRecord: Ref<AccountValueRecord | undefined> = ref(undefined)
|
||||||
|
|
||||||
|
|
@ -33,13 +31,12 @@ async function show(): Promise<AccountValueRecord | undefined> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addValueRecord() {
|
async function addValueRecord() {
|
||||||
if (!profileStore.state) return
|
|
||||||
const payload: AccountValueRecordCreationPayload = {
|
const payload: AccountValueRecordCreationPayload = {
|
||||||
timestamp: datetimeLocalToISO(timestamp.value),
|
timestamp: datetimeLocalToISO(timestamp.value),
|
||||||
type: AccountValueRecordType.BALANCE,
|
type: AccountValueRecordType.BALANCE,
|
||||||
value: amount.value
|
value: amount.value
|
||||||
}
|
}
|
||||||
const api = new AccountApiClient(profileStore.state)
|
const api = new AccountApiClient()
|
||||||
try {
|
try {
|
||||||
savedValueRecord.value = await api.createValueRecord(props.account.id, payload, attachments.value)
|
savedValueRecord.value = await api.createValueRecord(props.account.id, payload, attachments.value)
|
||||||
modal.value?.close('saved')
|
modal.value?.close('saved')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { getSelectedProfile } from '@/api/profile';
|
||||||
import type { TransactionCategory } from '@/api/transaction';
|
import type { TransactionCategory } from '@/api/transaction';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
@ -8,8 +8,7 @@ const props = defineProps<{ category: TransactionCategory, clickable?: boolean }
|
||||||
|
|
||||||
function onClicked() {
|
function onClicked() {
|
||||||
if (props.clickable) {
|
if (props.clickable) {
|
||||||
const profileStore = useProfileStore()
|
router.push(`/profiles/${getSelectedProfile()}/categories`)
|
||||||
router.push(`/profiles/${profileStore.state?.name}/categories`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { AccountApiClient, AccountHistoryItemType, type AccountHistoryItem, type AccountHistoryJournalEntryItem, type AccountHistoryValueRecordItem } from '@/api/account';
|
import { AccountApiClient, AccountHistoryItemType, type AccountHistoryItem, type AccountHistoryJournalEntryItem, type AccountHistoryValueRecordItem } from '@/api/account';
|
||||||
import type { PageRequest } from '@/api/pagination';
|
import type { PageRequest } from '@/api/pagination';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
|
||||||
import { onMounted, ref, type Ref } from 'vue';
|
import { onMounted, ref, type Ref } from 'vue';
|
||||||
import ValueRecordHistoryItem from './ValueRecordHistoryItem.vue';
|
import ValueRecordHistoryItem from './ValueRecordHistoryItem.vue';
|
||||||
import JournalEntryHistoryItem from './JournalEntryHistoryItem.vue';
|
import JournalEntryHistoryItem from './JournalEntryHistoryItem.vue';
|
||||||
|
|
@ -10,10 +9,8 @@ const props = defineProps<{ accountId: number }>()
|
||||||
const historyItems: Ref<AccountHistoryItem[]> = ref([])
|
const historyItems: Ref<AccountHistoryItem[]> = ref([])
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const profileStore = useProfileStore()
|
|
||||||
if (!profileStore.state) return
|
|
||||||
const pageRequest: PageRequest = { page: 1, size: 10, sorts: [{ attribute: 'timestamp', dir: 'DESC' }] }
|
const pageRequest: PageRequest = { page: 1, size: 10, sorts: [{ attribute: 'timestamp', dir: 'DESC' }] }
|
||||||
const api = new AccountApiClient(profileStore.state)
|
const api = new AccountApiClient()
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const page = await api.getHistory(props.accountId, pageRequest)
|
const page = await api.getHistory(props.accountId, pageRequest)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { AccountHistoryJournalEntryItem } from '@/api/account'
|
import type { AccountHistoryJournalEntryItem } from '@/api/account'
|
||||||
import { formatMoney } from '@/api/data';
|
import { formatMoney } from '@/api/data';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
import { getSelectedProfile } from '@/api/profile';
|
||||||
|
|
||||||
defineProps<{ item: AccountHistoryJournalEntryItem }>()
|
defineProps<{ item: AccountHistoryJournalEntryItem }>()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="history-item-content">
|
<div class="history-item-content">
|
||||||
<div>
|
<div>
|
||||||
<RouterLink :to="`/profiles/${profileStore.state?.name}/transactions/${item.transactionId}`">
|
<RouterLink :to="`/profiles/${getSelectedProfile()}/transactions/${item.transactionId}`">
|
||||||
Transaction #{{ item.transactionId }}
|
Transaction #{{ item.transactionId }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
entered as a
|
entered as a
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,14 @@
|
||||||
import { AccountApiClient, type AccountHistoryValueRecordItem } from '@/api/account';
|
import { AccountApiClient, type AccountHistoryValueRecordItem } from '@/api/account';
|
||||||
import { formatMoney } from '@/api/data';
|
import { formatMoney } from '@/api/data';
|
||||||
import AppButton from '../AppButton.vue';
|
import AppButton from '../AppButton.vue';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
|
||||||
import { showConfirm } from '@/util/alert';
|
import { showConfirm } from '@/util/alert';
|
||||||
|
|
||||||
const props = defineProps<{ item: AccountHistoryValueRecordItem, accountId: number }>()
|
const props = defineProps<{ item: AccountHistoryValueRecordItem, accountId: number }>()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
async function deleteValueRecord(id: number) {
|
async function deleteValueRecord(id: number) {
|
||||||
if (!profileStore.state) return
|
|
||||||
const confirm = await showConfirm('Are you sure you want to delete this value record?')
|
const confirm = await showConfirm('Are you sure you want to delete this value record?')
|
||||||
if (!confirm) return
|
if (!confirm) return
|
||||||
const api = new AccountApiClient(profileStore.state)
|
const api = new AccountApiClient()
|
||||||
try {
|
try {
|
||||||
await api.deleteValueRecord(props.accountId, id)
|
await api.deleteValueRecord(props.accountId, id)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { AccountApiClient, type Account } from '@/api/account';
|
import { AccountApiClient, type Account } from '@/api/account';
|
||||||
import { formatMoney } from '@/api/data';
|
import { formatMoney } from '@/api/data';
|
||||||
|
import { getSelectedProfile } from '@/api/profile';
|
||||||
import AddValueRecordModal from '@/components/AddValueRecordModal.vue';
|
import AddValueRecordModal from '@/components/AddValueRecordModal.vue';
|
||||||
import AppButton from '@/components/AppButton.vue';
|
import AppButton from '@/components/AppButton.vue';
|
||||||
import AppPage from '@/components/AppPage.vue';
|
import AppPage from '@/components/AppPage.vue';
|
||||||
import AccountHistory from '@/components/history/AccountHistory.vue';
|
import AccountHistory from '@/components/history/AccountHistory.vue';
|
||||||
import PropertiesTable from '@/components/PropertiesTable.vue';
|
import PropertiesTable from '@/components/PropertiesTable.vue';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
|
||||||
import { showConfirm } from '@/util/alert';
|
import { showConfirm } from '@/util/alert';
|
||||||
import { onMounted, ref, useTemplateRef, type Ref } from 'vue';
|
import { onMounted, ref, useTemplateRef, type Ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
@ -14,19 +14,13 @@ import { useRoute, useRouter } from 'vue-router';
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const addValueRecordModal = useTemplateRef("addValueRecordModal")
|
const addValueRecordModal = useTemplateRef("addValueRecordModal")
|
||||||
const account: Ref<Account | null> = ref(null)
|
const account: Ref<Account | null> = ref(null)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!profileStore.state) {
|
|
||||||
await router.replace('/')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountId = parseInt(route.params.id as string)
|
const accountId = parseInt(route.params.id as string)
|
||||||
try {
|
try {
|
||||||
const api = new AccountApiClient(profileStore.state)
|
const api = new AccountApiClient()
|
||||||
account.value = await api.getAccount(accountId)
|
account.value = await api.getAccount(accountId)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
|
@ -35,12 +29,12 @@ onMounted(async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
async function deleteAccount() {
|
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.')) {
|
if (await showConfirm('Are you sure you want to delete this account? This will permanently remove the account and all associated transactions.')) {
|
||||||
try {
|
try {
|
||||||
const api = new AccountApiClient(profileStore.state)
|
const api = new AccountApiClient()
|
||||||
await api.deleteAccount(account.value.id)
|
await api.deleteAccount(account.value.id)
|
||||||
await router.replace(`/profiles/${profileStore.state.name}`)
|
await router.replace(`/profiles/${getSelectedProfile()}`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -96,8 +90,8 @@ async function addValueRecord() {
|
||||||
</PropertiesTable>
|
</PropertiesTable>
|
||||||
<div>
|
<div>
|
||||||
<AppButton @click="addValueRecord()">Record Value</AppButton>
|
<AppButton @click="addValueRecord()">Record Value</AppButton>
|
||||||
<AppButton icon="wrench"
|
<AppButton icon="wrench" @click="router.push(`/profiles/${getSelectedProfile()}/accounts/${account?.id}/edit`)">
|
||||||
@click="router.push(`/profiles/${profileStore.state?.name}/accounts/${account?.id}/edit`)">Edit</AppButton>
|
Edit</AppButton>
|
||||||
<AppButton icon="trash" @click="deleteAccount()">Delete</AppButton>
|
<AppButton icon="trash" @click="deleteAccount()">Delete</AppButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ async function doChangePassword() {
|
||||||
<template>
|
<template>
|
||||||
<AppPage title="My User">
|
<AppPage title="My User">
|
||||||
<p>
|
<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>
|
</p>
|
||||||
<div style="text-align: right;">
|
<div style="text-align: right;">
|
||||||
<AppButton @click="showChangePasswordModal()">Change Password</AppButton>
|
<AppButton @click="showChangePasswordModal()">Change Password</AppButton>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const api = new AccountApiClient(profile.value)
|
const api = new AccountApiClient()
|
||||||
accounts.value = await api.getAccounts()
|
accounts.value = await api.getAccounts()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load accounts', err)
|
console.error('Failed to load accounts', err)
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,8 @@ import { ProfileApiClient, type Profile } from '@/api/profile';
|
||||||
import AppButton from '@/components/AppButton.vue';
|
import AppButton from '@/components/AppButton.vue';
|
||||||
import AppPage from '@/components/AppPage.vue';
|
import AppPage from '@/components/AppPage.vue';
|
||||||
import ModalWrapper from '@/components/ModalWrapper.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 { onMounted, type Ref, ref, useTemplateRef } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
const authStore = useAuthStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const profiles: Ref<Profile[]> = ref([])
|
const profiles: Ref<Profile[]> = ref([])
|
||||||
|
|
@ -16,12 +12,6 @@ const addProfileModal = useTemplateRef('addProfileModal')
|
||||||
const newProfileName = ref('')
|
const newProfileName = ref('')
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
authStore.$subscribe(async (_, state) => {
|
|
||||||
if (state.state === null) {
|
|
||||||
await router.replace('/login')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await fetchProfiles()
|
await fetchProfiles()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -35,7 +25,6 @@ async function fetchProfiles() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectProfile(profile: Profile) {
|
function selectProfile(profile: Profile) {
|
||||||
profileStore.onProfileSelected(profile)
|
|
||||||
router.push('/profiles/' + profile.name)
|
router.push('/profiles/' + profile.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ApiError } from '@/api/base';
|
import { ApiError } from '@/api/base';
|
||||||
import { formatMoney } from '@/api/data';
|
import { formatMoney } from '@/api/data';
|
||||||
|
import { getSelectedProfile } from '@/api/profile';
|
||||||
import { TransactionApiClient, type TransactionDetail } from '@/api/transaction';
|
import { TransactionApiClient, type TransactionDetail } from '@/api/transaction';
|
||||||
import AppButton from '@/components/AppButton.vue';
|
import AppButton from '@/components/AppButton.vue';
|
||||||
import AppPage from '@/components/AppPage.vue';
|
import AppPage from '@/components/AppPage.vue';
|
||||||
import CategoryLabel from '@/components/CategoryLabel.vue';
|
import CategoryLabel from '@/components/CategoryLabel.vue';
|
||||||
import PropertiesTable from '@/components/PropertiesTable.vue';
|
import PropertiesTable from '@/components/PropertiesTable.vue';
|
||||||
import TagLabel from '@/components/TagLabel.vue';
|
import TagLabel from '@/components/TagLabel.vue';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
|
||||||
import { showAlert, showConfirm } from '@/util/alert';
|
import { showAlert, showConfirm } from '@/util/alert';
|
||||||
import { onMounted, ref, type Ref } from 'vue';
|
import { onMounted, ref, type Ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const transaction: Ref<TransactionDetail | undefined> = ref()
|
const transaction: Ref<TransactionDetail | undefined> = ref()
|
||||||
|
|
||||||
|
|
@ -33,12 +32,12 @@ onMounted(async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
async function deleteTransaction() {
|
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.')
|
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
|
if (!conf) return
|
||||||
try {
|
try {
|
||||||
await new TransactionApiClient().deleteTransaction(transaction.value.id)
|
await new TransactionApiClient().deleteTransaction(transaction.value.id)
|
||||||
await router.replace(`/profiles/${profileStore.state.name}`)
|
await router.replace(`/profiles/${getSelectedProfile()}`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +119,8 @@ async function deleteTransaction() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<AppButton icon="wrench"
|
<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>
|
||||||
<AppButton icon="trash" @click="deleteTransaction()">Delete</AppButton>
|
<AppButton icon="trash" @click="deleteTransaction()">Delete</AppButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { AccountApiClient, AccountTypes, type Account, type AccountType } from '@/api/account';
|
import { AccountApiClient, AccountTypes, type Account, type AccountType } from '@/api/account';
|
||||||
|
import { getSelectedProfile } from '@/api/profile';
|
||||||
import AppPage from '@/components/AppPage.vue';
|
import AppPage from '@/components/AppPage.vue';
|
||||||
import AppForm from '@/components/form/AppForm.vue';
|
import AppForm from '@/components/form/AppForm.vue';
|
||||||
import FormActions from '@/components/form/FormActions.vue';
|
import FormActions from '@/components/form/FormActions.vue';
|
||||||
import FormControl from '@/components/form/FormControl.vue';
|
import FormControl from '@/components/form/FormControl.vue';
|
||||||
import FormGroup from '@/components/form/FormGroup.vue';
|
import FormGroup from '@/components/form/FormGroup.vue';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
|
||||||
import { computed, onMounted, ref, type Ref } from 'vue';
|
import { computed, onMounted, ref, type Ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const existingAccount: Ref<Account | null> = ref(null)
|
const existingAccount: Ref<Account | null> = ref(null)
|
||||||
const editing = computed(() => {
|
const editing = computed(() => {
|
||||||
|
|
@ -26,14 +25,12 @@ const currency = ref('USD')
|
||||||
const description = ref('')
|
const description = ref('')
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!profileStore.state) return
|
|
||||||
|
|
||||||
const accountIdStr = route.params.id
|
const accountIdStr = route.params.id
|
||||||
if (accountIdStr && typeof (accountIdStr) === 'string') {
|
if (accountIdStr && typeof (accountIdStr) === 'string') {
|
||||||
const accountId = parseInt(accountIdStr)
|
const accountId = parseInt(accountIdStr)
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const api = new AccountApiClient(profileStore.state)
|
const api = new AccountApiClient()
|
||||||
existingAccount.value = await api.getAccount(accountId)
|
existingAccount.value = await api.getAccount(accountId)
|
||||||
accountName.value = existingAccount.value.name
|
accountName.value = existingAccount.value.name
|
||||||
accountType.value = AccountTypes.of(existingAccount.value.type)
|
accountType.value = AccountTypes.of(existingAccount.value.type)
|
||||||
|
|
@ -49,7 +46,6 @@ onMounted(async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
async function doSubmit() {
|
async function doSubmit() {
|
||||||
if (!profileStore.state) return
|
|
||||||
const payload = {
|
const payload = {
|
||||||
name: accountName.value,
|
name: accountName.value,
|
||||||
description: description.value,
|
description: description.value,
|
||||||
|
|
@ -59,12 +55,12 @@ async function doSubmit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const api = new AccountApiClient(profileStore.state)
|
const api = new AccountApiClient()
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const account = editing.value
|
const account = editing.value
|
||||||
? await api.updateAccount(existingAccount.value?.id ?? 0, payload)
|
? await api.updateAccount(existingAccount.value?.id ?? 0, payload)
|
||||||
: await api.createAccount(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) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -105,7 +101,7 @@ async function doSubmit() {
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormActions @cancel="router.replace(`/profiles/${profileStore.state?.name}`)" :disabled="loading"
|
<FormActions @cancel="router.replace(`/profiles/${getSelectedProfile()}`)" :disabled="loading"
|
||||||
:submit-text="editing ? 'Save' : 'Add'" />
|
:submit-text="editing ? 'Save' : 'Add'" />
|
||||||
</AppForm>
|
</AppForm>
|
||||||
</AppPage>
|
</AppPage>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ The form consists of a few main sections:
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { AccountApiClient, type Account } from '@/api/account';
|
import { AccountApiClient, type Account } from '@/api/account';
|
||||||
import { DataApiClient, type Currency } from '@/api/data';
|
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 { TransactionApiClient, type AddTransactionPayload, type TransactionDetail, type TransactionDetailLineItem, type TransactionVendor } from '@/api/transaction';
|
||||||
import AppPage from '@/components/AppPage.vue';
|
import AppPage from '@/components/AppPage.vue';
|
||||||
import CategorySelect from '@/components/CategorySelect.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 FormGroup from '@/components/form/FormGroup.vue';
|
||||||
import LineItemsEditor from '@/components/LineItemsEditor.vue';
|
import LineItemsEditor from '@/components/LineItemsEditor.vue';
|
||||||
import TagLabel from '@/components/TagLabel.vue';
|
import TagLabel from '@/components/TagLabel.vue';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
|
||||||
import { getDatetimeLocalValueForNow } from '@/util/time';
|
import { getDatetimeLocalValueForNow } from '@/util/time';
|
||||||
import { computed, onMounted, ref, watch, type Ref } from 'vue';
|
import { computed, onMounted, ref, watch, type Ref } from 'vue';
|
||||||
import { useRoute, useRouter, } from 'vue-router';
|
import { useRoute, useRouter, } from 'vue-router';
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const existingTransaction: Ref<TransactionDetail | null> = ref(null)
|
const existingTransaction: Ref<TransactionDetail | null> = ref(null)
|
||||||
const editing = computed(() => {
|
const editing = computed(() => {
|
||||||
|
|
@ -80,10 +79,9 @@ watch(availableCurrencies, (newValue: Currency[]) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!profileStore.state) return
|
|
||||||
const dataClient = new DataApiClient()
|
const dataClient = new DataApiClient()
|
||||||
const transactionClient = new TransactionApiClient()
|
const transactionClient = new TransactionApiClient()
|
||||||
const accountClient = new AccountApiClient(profileStore.state)
|
const accountClient = new AccountApiClient()
|
||||||
|
|
||||||
// Fetch various collections of data needed for different user choices.
|
// Fetch various collections of data needed for different user choices.
|
||||||
dataClient.getCurrencies().then(currencies => allCurrencies.value = currencies)
|
dataClient.getCurrencies().then(currencies => allCurrencies.value = currencies)
|
||||||
|
|
@ -117,8 +115,6 @@ onMounted(async () => {
|
||||||
* created.
|
* created.
|
||||||
*/
|
*/
|
||||||
async function doSubmit() {
|
async function doSubmit() {
|
||||||
if (!profileStore.state) return
|
|
||||||
|
|
||||||
const localDate = new Date(timestamp.value)
|
const localDate = new Date(timestamp.value)
|
||||||
const scaledAmount = amount.value * Math.pow(10, currency.value?.fractionalDigits ?? 0)
|
const scaledAmount = amount.value * Math.pow(10, currency.value?.fractionalDigits ?? 0)
|
||||||
const payload: AddTransactionPayload = {
|
const payload: AddTransactionPayload = {
|
||||||
|
|
@ -146,7 +142,7 @@ async function doSubmit() {
|
||||||
} else {
|
} else {
|
||||||
savedTransaction = await transactionApi.addTransaction(payload, attachmentsToUpload.value)
|
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) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -159,11 +155,10 @@ async function doSubmit() {
|
||||||
* profile's homepage.
|
* profile's homepage.
|
||||||
*/
|
*/
|
||||||
function doCancel() {
|
function doCancel() {
|
||||||
if (!profileStore.state) return
|
|
||||||
if (editing.value) {
|
if (editing.value) {
|
||||||
router.replace(`/profiles/${profileStore.state.name}/transactions/${existingTransaction.value?.id}`)
|
router.replace(`/profiles/${getSelectedProfile()}/transactions/${existingTransaction.value?.id}`)
|
||||||
} else {
|
} else {
|
||||||
router.replace(`/profiles/${profileStore.state.name}`)
|
router.replace(`/profiles/${getSelectedProfile()}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { AccountApiClient, type Account } from '@/api/account'
|
import { AccountApiClient, type Account } from '@/api/account'
|
||||||
import { formatMoney } from '@/api/data'
|
import { formatMoney } from '@/api/data'
|
||||||
|
import { getSelectedProfile } from '@/api/profile'
|
||||||
import AppButton from '@/components/AppButton.vue'
|
import AppButton from '@/components/AppButton.vue'
|
||||||
import HomeModule from '@/components/HomeModule.vue'
|
import HomeModule from '@/components/HomeModule.vue'
|
||||||
import { useProfileStore } from '@/stores/profile-store'
|
|
||||||
import { onMounted, ref, type Ref } from 'vue'
|
import { onMounted, ref, type Ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const accounts: Ref<Account[]> = ref([])
|
const accounts: Ref<Account[]> = ref([])
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!profileStore.state) {
|
const accountApi = new AccountApiClient()
|
||||||
console.warn('No profile is selected.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountApi = new AccountApiClient(profileStore.state)
|
|
||||||
accountApi.getAccounts().then(result => accounts.value = result)
|
accountApi.getAccounts().then(result => accounts.value = result)
|
||||||
.catch(err => console.error(err))
|
.catch(err => console.error(err))
|
||||||
})
|
})
|
||||||
|
|
@ -39,7 +33,7 @@ onMounted(async () => {
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="account in accounts" :key="account.id">
|
<tr v-for="account in accounts" :key="account.id">
|
||||||
<td>
|
<td>
|
||||||
<RouterLink :to="`/profiles/${profileStore.state?.name}/accounts/${account.id}`">{{ account.name }}
|
<RouterLink :to="`/profiles/${getSelectedProfile()}/accounts/${account.id}`">{{ account.name }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ account.currency.code }}</td>
|
<td>{{ account.currency.code }}</td>
|
||||||
|
|
@ -54,7 +48,7 @@ onMounted(async () => {
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:actions>
|
<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>
|
</AppButton>
|
||||||
</template>
|
</template>
|
||||||
</HomeModule>
|
</HomeModule>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,33 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProfileApiClient } from '@/api/profile';
|
import { getSelectedProfile, ProfileApiClient, type Profile } from '@/api/profile';
|
||||||
import AppButton from '@/components/AppButton.vue';
|
import AppButton from '@/components/AppButton.vue';
|
||||||
import ConfirmModal from '@/components/ConfirmModal.vue';
|
import ConfirmModal from '@/components/ConfirmModal.vue';
|
||||||
import HomeModule from '@/components/HomeModule.vue';
|
import HomeModule from '@/components/HomeModule.vue';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
import { showAlert } from '@/util/alert';
|
||||||
import { useTemplateRef } from 'vue';
|
import { onMounted, ref, useTemplateRef, type Ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const confirmDeleteModal = useTemplateRef('confirmDeleteModal')
|
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() {
|
async function deleteProfile() {
|
||||||
if (profileStore.state && await confirmDeleteModal.value?.confirm()) {
|
const currentProfileName = getSelectedProfile()
|
||||||
|
if (await confirmDeleteModal.value?.confirm()) {
|
||||||
const api = new ProfileApiClient()
|
const api = new ProfileApiClient()
|
||||||
try {
|
try {
|
||||||
await api.deleteProfile(profileStore.state.name)
|
await api.deleteProfile(currentProfileName)
|
||||||
await router.replace('/profiles')
|
await router.replace('/profiles')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
|
@ -24,14 +36,14 @@ async function deleteProfile() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<HomeModule title="Profile">
|
<HomeModule title="Profile" v-if="profile">
|
||||||
<template v-slot:default>
|
<template v-slot:default>
|
||||||
<p>Your currently selected profile is: {{ profileStore.state?.name }}</p>
|
<p>Your currently selected profile is: {{ profile.name }}</p>
|
||||||
<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>
|
||||||
<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>
|
</p>
|
||||||
|
|
||||||
<ConfirmModal ref="confirmDeleteModal">
|
<ConfirmModal ref="confirmDeleteModal">
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { formatMoney } from '@/api/data';
|
import { formatMoney } from '@/api/data';
|
||||||
import type { Page, PageRequest } from '@/api/pagination';
|
import type { Page, PageRequest } from '@/api/pagination';
|
||||||
|
import { getSelectedProfile } from '@/api/profile';
|
||||||
import { TransactionApiClient, type TransactionsListItem } from '@/api/transaction';
|
import { TransactionApiClient, type TransactionsListItem } from '@/api/transaction';
|
||||||
import AppButton from '@/components/AppButton.vue';
|
import AppButton from '@/components/AppButton.vue';
|
||||||
import HomeModule from '@/components/HomeModule.vue';
|
import HomeModule from '@/components/HomeModule.vue';
|
||||||
import PaginationControls from '@/components/PaginationControls.vue';
|
import PaginationControls from '@/components/PaginationControls.vue';
|
||||||
import { useProfileStore } from '@/stores/profile-store';
|
|
||||||
import { onMounted, ref, type Ref } from 'vue';
|
import { onMounted, ref, type Ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter()
|
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 })
|
const transactions: Ref<Page<TransactionsListItem>> = ref({ items: [], pageRequest: { page: 1, size: 10, sorts: [] }, totalElements: 0, totalPages: 0, isFirst: true, isLast: true })
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
@ -51,18 +50,18 @@ async function fetchPage(pageRequest: PageRequest) {
|
||||||
<td>{{ tx.description }}</td>
|
<td>{{ tx.description }}</td>
|
||||||
<td>
|
<td>
|
||||||
<RouterLink v-if="tx.creditedAccount"
|
<RouterLink v-if="tx.creditedAccount"
|
||||||
:to="`/profiles/${profileStore.state?.name}/accounts/${tx.creditedAccount.id}`">
|
:to="`/profiles/${getSelectedProfile()}/accounts/${tx.creditedAccount.id}`">
|
||||||
{{ tx.creditedAccount?.name }}
|
{{ tx.creditedAccount?.name }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<RouterLink v-if="tx.debitedAccount"
|
<RouterLink v-if="tx.debitedAccount"
|
||||||
:to="`/profiles/${profileStore.state?.name}/accounts/${tx.debitedAccount.id}`">
|
:to="`/profiles/${getSelectedProfile()}/accounts/${tx.debitedAccount.id}`">
|
||||||
{{ tx.debitedAccount?.name }}
|
{{ tx.debitedAccount?.name }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<RouterLink :to="`/profiles/${profileStore.state?.name}/transactions/${tx.id}`">View</RouterLink>
|
<RouterLink :to="`/profiles/${getSelectedProfile()}/transactions/${tx.id}`">View</RouterLink>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -70,7 +69,7 @@ async function fetchPage(pageRequest: PageRequest) {
|
||||||
<PaginationControls :page="transactions" @update="pr => fetchPage(pr)"></PaginationControls>
|
<PaginationControls :page="transactions" @update="pr => fetchPage(pr)"></PaginationControls>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:actions>
|
<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>
|
Transaction</AppButton>
|
||||||
</template>
|
</template>
|
||||||
</HomeModule>
|
</HomeModule>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { getSelectedProfile } from '@/api/profile'
|
||||||
import { useAuthStore } from '@/stores/auth-store'
|
import { useAuthStore } from '@/stores/auth-store'
|
||||||
import { createRouter, createWebHistory, type RouteLocationNormalized } from 'vue-router'
|
import { createRouter, createWebHistory, type RouteLocationNormalized } from 'vue-router'
|
||||||
import { useProfileStore } from '@/stores/profile-store'
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
|
@ -30,13 +30,12 @@ const router = createRouter({
|
||||||
meta: { title: 'Profiles' },
|
meta: { title: 'Profiles' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'profiles/:name',
|
path: 'profiles/:profileName',
|
||||||
beforeEnter: profileSelected,
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: () => import('@/pages/UserHomePage.vue'),
|
component: () => import('@/pages/UserHomePage.vue'),
|
||||||
meta: { title: (to: RouteLocationNormalized) => 'Profile ' + to.params.name },
|
meta: { title: (to: RouteLocationNormalized) => 'Profile ' + getSelectedProfile(to) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'accounts/:id',
|
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) => {
|
router.beforeEach((to, _, next) => {
|
||||||
if (to.meta.title !== undefined && typeof to.meta.title === 'string') {
|
if (to.meta.title !== undefined && typeof to.meta.title === 'string') {
|
||||||
document.title = 'Finnow - ' + to.meta.title
|
document.title = 'Finnow - ' + to.meta.title
|
||||||
|
|
@ -99,6 +99,12 @@ router.beforeEach((to, _, next) => {
|
||||||
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) {
|
function onlyAuthenticated(to: RouteLocationNormalized) {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
if (authStore.state) return true
|
if (authStore.state) return true
|
||||||
|
|
@ -106,10 +112,4 @@ function onlyAuthenticated(to: RouteLocationNormalized) {
|
||||||
return '/login?next=' + encodeURIComponent(to.path)
|
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
|
export default router
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue