Made login auto-redirect to first profile. Fixes #31
Build and Deploy Web App / build-and-deploy (push) Successful in 19s
Details
Build and Deploy Web App / build-and-deploy (push) Successful in 19s
Details
This commit is contained in:
parent
c1193d96fd
commit
92a3dc4c62
|
|
@ -48,3 +48,21 @@ export function getSelectedProfile(route: RouteLocation): string {
|
|||
}
|
||||
return name
|
||||
}
|
||||
|
||||
const LAST_PROFILE_USED_KEY = 'last-profile-used'
|
||||
|
||||
export function saveLastProfileUsed(route: RouteLocation) {
|
||||
try {
|
||||
const profileName = getSelectedProfile(route)
|
||||
const existing = localStorage.getItem(LAST_PROFILE_USED_KEY)
|
||||
if (existing === null || existing !== profileName) {
|
||||
localStorage.setItem(LAST_PROFILE_USED_KEY, profileName)
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(LAST_PROFILE_USED_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
export function getLastProfileUsed(): string | null {
|
||||
return localStorage.getItem(LAST_PROFILE_USED_KEY)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,18 +34,28 @@ function goToAccount() {
|
|||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="account-card" @click="goToAccount()">
|
||||
<div
|
||||
class="account-card"
|
||||
@click="goToAccount()"
|
||||
>
|
||||
<!-- A top row for the name on the left, and balance on the right. -->
|
||||
<div class="account-card-top-row">
|
||||
<div>
|
||||
<span class="font-bold" style="margin-right: 0.5rem">{{ account.name }}</span>
|
||||
<span
|
||||
class="font-bold"
|
||||
style="margin-right: 0.5rem"
|
||||
>{{ account.name }}</span
|
||||
>
|
||||
<span class="font-mono font-size-xsmall">#{{ account.numberSuffix }}</span>
|
||||
</div>
|
||||
|
||||
<div class="font-mono font-size-small" :class="{
|
||||
<div
|
||||
class="font-mono font-size-small"
|
||||
:class="{
|
||||
'text-positive': isBalancePositive,
|
||||
'text-negative': isBalanceNegative,
|
||||
}">
|
||||
}"
|
||||
>
|
||||
<span v-if="account.currentBalance !== null">
|
||||
{{ formatMoney(account.currentBalance, account.currency) }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ defineProps<{
|
|||
{{ label }}
|
||||
<slot></slot>
|
||||
</label>
|
||||
<p v-if="hint" style="margin-top: 0.1rem; margin-bottom: 0;" class="font-size-small text-muted">
|
||||
<p
|
||||
v-if="hint"
|
||||
style="margin-top: 0.1rem; margin-bottom: 0"
|
||||
class="font-size-small text-muted"
|
||||
>
|
||||
{{ hint }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -60,13 +60,28 @@ async function addValueRecord() {
|
|||
}
|
||||
</script>
|
||||
<template>
|
||||
<AppPage :title="account?.name ?? ''" v-if="account">
|
||||
<AppPage
|
||||
:title="account?.name ?? ''"
|
||||
v-if="account"
|
||||
>
|
||||
<div>
|
||||
<AppBadge size="lg" v-if="account.currentBalance !== null" class="font-mono">
|
||||
<AppBadge
|
||||
size="lg"
|
||||
v-if="account.currentBalance !== null"
|
||||
class="font-mono"
|
||||
>
|
||||
{{ account.currency.code }} {{ formatMoney(account.currentBalance, account.currency) }}
|
||||
</AppBadge>
|
||||
<AppBadge size="lg" class="font-mono">#{{ account.numberSuffix }}</AppBadge>
|
||||
<AppBadge size="lg" v-if="accountType" class="font-mono">
|
||||
<AppBadge
|
||||
size="lg"
|
||||
class="font-mono"
|
||||
>#{{ account.numberSuffix }}</AppBadge
|
||||
>
|
||||
<AppBadge
|
||||
size="lg"
|
||||
v-if="accountType"
|
||||
class="font-mono"
|
||||
>
|
||||
{{ accountType.emoji }} {{ accountType.name }}
|
||||
</AppBadge>
|
||||
</div>
|
||||
|
|
@ -81,15 +96,36 @@ async function addValueRecord() {
|
|||
</PropertiesTable>
|
||||
|
||||
<ButtonBar>
|
||||
<AppButton @click="addValueRecord()" :disabled="account.archived">Record Value</AppButton>
|
||||
<AppButton icon="wrench" :disabled="account.archived"
|
||||
@click="router.push(`/profiles/${getSelectedProfile(route)}/accounts/${account?.id}/edit`)">
|
||||
Edit</AppButton>
|
||||
<AppButton icon="trash" @click="deleteAccount()" :disabled="account.archived">Delete</AppButton>
|
||||
<AppButton
|
||||
@click="addValueRecord()"
|
||||
:disabled="account.archived"
|
||||
>Record Value</AppButton
|
||||
>
|
||||
<AppButton
|
||||
icon="wrench"
|
||||
:disabled="account.archived"
|
||||
@click="router.push(`/profiles/${getSelectedProfile(route)}/accounts/${account?.id}/edit`)"
|
||||
>
|
||||
Edit</AppButton
|
||||
>
|
||||
<AppButton
|
||||
icon="trash"
|
||||
@click="deleteAccount()"
|
||||
:disabled="account.archived"
|
||||
>Delete</AppButton
|
||||
>
|
||||
</ButtonBar>
|
||||
|
||||
<AccountHistory :account="account" v-if="account" ref="history" />
|
||||
<AccountHistory
|
||||
:account="account"
|
||||
v-if="account"
|
||||
ref="history"
|
||||
/>
|
||||
|
||||
<AddValueRecordModal v-if="account" :account="account" ref="addValueRecordModal" />
|
||||
<AddValueRecordModal
|
||||
v-if="account"
|
||||
:account="account"
|
||||
ref="addValueRecordModal"
|
||||
/>
|
||||
</AppPage>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { AuthApiClient } from '@/api/auth'
|
||||
import { ApiError } from '@/api/base'
|
||||
import { getLastProfileUsed, ProfileApiClient } from '@/api/profile'
|
||||
import AppButton from '@/components/common/AppButton.vue'
|
||||
import AppForm from '@/components/common/form/AppForm.vue'
|
||||
import FormControl from '@/components/common/form/FormControl.vue'
|
||||
|
|
@ -27,11 +28,20 @@ async function doLogin() {
|
|||
try {
|
||||
const token = await apiClient.login(username.value, password.value)
|
||||
authStore.onUserLoggedIn(username.value, token)
|
||||
const profiles = await new ProfileApiClient().getProfiles()
|
||||
const lastProfileUsed = getLastProfileUsed()
|
||||
hideLoader()
|
||||
if ('next' in route.query && typeof route.query.next === 'string') {
|
||||
await router.replace(route.query.next)
|
||||
} else if (profiles.length === 1) {
|
||||
// If there's only one available profile, go right to it.
|
||||
await router.replace(`/profiles/${profiles[0].name}`)
|
||||
} else if (lastProfileUsed !== null && profiles.map((p) => p.name).includes(lastProfileUsed)) {
|
||||
// If we know what profile the user was last using, go to that.
|
||||
await router.replace(`/profiles/${lastProfileUsed}`)
|
||||
} else {
|
||||
await router.replace('/')
|
||||
// Otherwise, go to the profiles page so the user can choose.
|
||||
await router.replace('/profiles')
|
||||
}
|
||||
} catch (err) {
|
||||
hideLoader()
|
||||
|
|
|
|||
|
|
@ -80,11 +80,22 @@ function onCancel() {
|
|||
<AppPage :title="editing ? 'Edit Account' : 'Add Account'">
|
||||
<AppForm @submit="doSubmit()">
|
||||
<FormGroup>
|
||||
<FormControl label="Account Name" style="max-width: 200px">
|
||||
<input v-model="accountName" :disabled="loading" />
|
||||
<FormControl
|
||||
label="Account Name"
|
||||
style="max-width: 200px"
|
||||
>
|
||||
<input
|
||||
v-model="accountName"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Account Type">
|
||||
<select id="account-type-select" v-model="accountType" :disabled="loading" required>
|
||||
<select
|
||||
id="account-type-select"
|
||||
v-model="accountType"
|
||||
:disabled="loading"
|
||||
required
|
||||
>
|
||||
<option :value="AccountTypes.CHECKING">{{ AccountTypes.CHECKING.name }}</option>
|
||||
<option :value="AccountTypes.SAVINGS">{{ AccountTypes.SAVINGS.name }}</option>
|
||||
<option :value="AccountTypes.CREDIT_CARD">{{ AccountTypes.CREDIT_CARD.name }}</option>
|
||||
|
|
@ -92,24 +103,46 @@ function onCancel() {
|
|||
</select>
|
||||
</FormControl>
|
||||
<FormControl label="Currency">
|
||||
<select id="currency-select" v-model="currency" :disabled="loading" required>
|
||||
<select
|
||||
id="currency-select"
|
||||
v-model="currency"
|
||||
:disabled="loading"
|
||||
required
|
||||
>
|
||||
<option value="USD">USD</option>
|
||||
<option value="EUR">EUR</option>
|
||||
<option value="GBP">GBP</option>
|
||||
</select>
|
||||
</FormControl>
|
||||
<FormControl label="Account Number Suffix" style="max-width: 200px">
|
||||
<input id="account-number-suffix-input" v-model="accountNumberSuffix" minlength="4" maxlength="4"
|
||||
:disabled="loading" required />
|
||||
<FormControl
|
||||
label="Account Number Suffix"
|
||||
style="max-width: 200px"
|
||||
>
|
||||
<input
|
||||
id="account-number-suffix-input"
|
||||
v-model="accountNumberSuffix"
|
||||
minlength="4"
|
||||
maxlength="4"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormControl label="Description">
|
||||
<textarea id="description-textarea" v-model="description" :disabled="loading"></textarea>
|
||||
<textarea
|
||||
id="description-textarea"
|
||||
v-model="description"
|
||||
:disabled="loading"
|
||||
></textarea>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<FormActions @cancel="onCancel" :disabled="loading" :submit-text="editing ? 'Save' : 'Add'" />
|
||||
<FormActions
|
||||
@cancel="onCancel"
|
||||
:disabled="loading"
|
||||
:submit-text="editing ? 'Save' : 'Add'"
|
||||
/>
|
||||
</AppForm>
|
||||
</AppPage>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -281,20 +281,46 @@ function getLocalDateTimeStringFromUTCTimestamp(timestamp: string) {
|
|||
<FormGroup>
|
||||
<!-- Basic properties -->
|
||||
<FormControl label="Timestamp">
|
||||
<input type="datetime-local" v-model="timestamp" step="1" :disabled="loading" style="min-width: 250px" />
|
||||
<input
|
||||
type="datetime-local"
|
||||
v-model="timestamp"
|
||||
step="1"
|
||||
:disabled="loading"
|
||||
style="min-width: 250px"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Amount">
|
||||
<input type="number" v-model="amount" step="0.01" min="0.01" :disabled="loading" style="max-width: 100px" />
|
||||
<input
|
||||
type="number"
|
||||
v-model="amount"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
:disabled="loading"
|
||||
style="max-width: 100px"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Currency">
|
||||
<select v-model="currency" :disabled="loading || availableCurrencies.length === 1">
|
||||
<option v-for="currency in availableCurrencies" :key="currency.code" :value="currency">
|
||||
<select
|
||||
v-model="currency"
|
||||
:disabled="loading || availableCurrencies.length === 1"
|
||||
>
|
||||
<option
|
||||
v-for="currency in availableCurrencies"
|
||||
:key="currency.code"
|
||||
:value="currency"
|
||||
>
|
||||
{{ currency.code }}
|
||||
</option>
|
||||
</select>
|
||||
</FormControl>
|
||||
<FormControl label="Description" style="min-width: 200px">
|
||||
<textarea v-model="description" :disabled="loading"></textarea>
|
||||
<FormControl
|
||||
label="Description"
|
||||
style="min-width: 200px"
|
||||
>
|
||||
<textarea
|
||||
v-model="description"
|
||||
:disabled="loading"
|
||||
></textarea>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
|
|
@ -311,16 +337,30 @@ function getLocalDateTimeStringFromUTCTimestamp(timestamp: string) {
|
|||
<FormGroup>
|
||||
<!-- Accounts -->
|
||||
<FormControl label="Credited Account">
|
||||
<select v-model="creditedAccountId" :disabled="loading">
|
||||
<option v-for="account in availableAccounts" :key="account.id" :value="account.id">
|
||||
<select
|
||||
v-model="creditedAccountId"
|
||||
:disabled="loading"
|
||||
>
|
||||
<option
|
||||
v-for="account in availableAccounts"
|
||||
:key="account.id"
|
||||
:value="account.id"
|
||||
>
|
||||
{{ account.name }} ({{ account.numberSuffix }})
|
||||
</option>
|
||||
<option :value="null">None</option>
|
||||
</select>
|
||||
</FormControl>
|
||||
<FormControl label="Debited Account">
|
||||
<select v-model="debitedAccountId" :disabled="loading">
|
||||
<option v-for="account in availableAccounts" :key="account.id" :value="account.id">
|
||||
<select
|
||||
v-model="debitedAccountId"
|
||||
:disabled="loading"
|
||||
>
|
||||
<option
|
||||
v-for="account in availableAccounts"
|
||||
:key="account.id"
|
||||
:value="account.id"
|
||||
>
|
||||
{{ account.name }} ({{ account.numberSuffix }})
|
||||
</option>
|
||||
<option :value="null">None</option>
|
||||
|
|
@ -328,8 +368,12 @@ function getLocalDateTimeStringFromUTCTimestamp(timestamp: string) {
|
|||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<LineItemsEditor v-if="currency" v-model="lineItems" :currency="currency"
|
||||
:transaction-amount="floatMoneyToInteger(amount, currency)" />
|
||||
<LineItemsEditor
|
||||
v-if="currency"
|
||||
v-model="lineItems"
|
||||
:currency="currency"
|
||||
:transaction-amount="floatMoneyToInteger(amount, currency)"
|
||||
/>
|
||||
|
||||
<FormGroup>
|
||||
<!-- Tags -->
|
||||
|
|
@ -340,12 +384,18 @@ function getLocalDateTimeStringFromUTCTimestamp(timestamp: string) {
|
|||
|
||||
<FormGroup>
|
||||
<h5>Attachments</h5>
|
||||
<FileSelector :initial-files="existingTransaction?.attachments ?? []"
|
||||
v-model:uploaded-files="attachmentsToUpload" v-model:removed-files="removedAttachmentIds" />
|
||||
<FileSelector
|
||||
:initial-files="existingTransaction?.attachments ?? []"
|
||||
v-model:uploaded-files="attachmentsToUpload"
|
||||
v-model:removed-files="removedAttachmentIds"
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormActions @cancel="doCancel()" :disabled="loading || !formValid || !unsavedEdits"
|
||||
:submit-text="editing ? 'Save' : 'Add'" />
|
||||
<FormActions
|
||||
@cancel="doCancel()"
|
||||
:disabled="loading || !formValid || !unsavedEdits"
|
||||
:submit-text="editing ? 'Save' : 'Add'"
|
||||
/>
|
||||
</AppForm>
|
||||
</AppPage>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getSelectedProfile } from '@/api/profile'
|
||||
import { getSelectedProfile, saveLastProfileUsed } from '@/api/profile'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
import { createRouter, createWebHistory, type RouteLocationNormalized } from 'vue-router'
|
||||
|
||||
|
|
@ -104,6 +104,11 @@ router.beforeEach((to, _, next) => {
|
|||
next()
|
||||
})
|
||||
|
||||
// After navigation, save the last used profile to keep track of what the user was last using.
|
||||
router.afterEach((to) => {
|
||||
saveLastProfileUsed(to)
|
||||
})
|
||||
|
||||
/**
|
||||
* Guard to ensure a route can only be accessed by authenticated users.
|
||||
* @param to The route to guard.
|
||||
|
|
|
|||
Loading…
Reference in New Issue