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 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[]> {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[]> {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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