feat(libra/record): permission-gated Add Expense / Add Income buttons
Check the user's permitted accounts on mount; disable the card and show a lock-icon caption directing them to contact an administrator when they have no SUBMIT_EXPENSE / SUBMIT_INCOME access. Greys the icon and badge background when disabled so the card reads inactive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c5d943a991
commit
9e3de6ce16
1 changed files with 91 additions and 24 deletions
|
|
@ -1,21 +1,52 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useTimeAgo } from '@vueuse/core'
|
||||
import { DollarSign, TrendingUp, FileText, Trash2, Image as ImageIcon } from 'lucide-vue-next'
|
||||
import { DollarSign, TrendingUp, Lock, FileText, Trash2, Image as ImageIcon } from 'lucide-vue-next'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import AddExpense from '@/modules/expenses/components/AddExpense.vue'
|
||||
import AddIncome from './AddIncome.vue'
|
||||
import { useExpenseDrafts, type ExpenseDraft } from '../composables/useExpenseDrafts'
|
||||
import { useAuth } from '@/composables/useAuthService'
|
||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||
import type { ExpensesAPI } from '@/modules/expenses/services/ExpensesAPI'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { drafts, hasDrafts, deleteDraft } = useExpenseDrafts()
|
||||
const { user } = useAuth()
|
||||
const expensesAPI = injectService<ExpensesAPI>(SERVICE_TOKENS.EXPENSES_API)
|
||||
|
||||
const showAddExpense = ref(false)
|
||||
const showAddIncome = ref(false)
|
||||
|
||||
const permissionsLoaded = ref(false)
|
||||
const canSubmitExpense = ref(true)
|
||||
const canSubmitIncome = ref(true)
|
||||
|
||||
const expenseDisabled = computed(() => permissionsLoaded.value && !canSubmitExpense.value)
|
||||
const incomeDisabled = computed(() => permissionsLoaded.value && !canSubmitIncome.value)
|
||||
|
||||
onMounted(async () => {
|
||||
const walletKey = user.value?.wallets?.[0]?.inkey
|
||||
if (!walletKey) return
|
||||
|
||||
try {
|
||||
const accounts = await expensesAPI.getAccounts(walletKey, true, true)
|
||||
canSubmitExpense.value = accounts.some(
|
||||
(a) => a.name === 'Expenses' || a.name.startsWith('Expenses:')
|
||||
)
|
||||
canSubmitIncome.value = accounts.some(
|
||||
(a) => a.name === 'Income' || a.name.startsWith('Income:')
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('[RecordPage] Failed to load user permissions:', error)
|
||||
} finally {
|
||||
permissionsLoaded.value = true
|
||||
}
|
||||
})
|
||||
|
||||
function handleExpenseSubmitted() {
|
||||
// Could refresh balance or show notification
|
||||
}
|
||||
|
|
@ -50,32 +81,68 @@ function draftTimeAgo(isoDate: string) {
|
|||
<!-- Action Cards -->
|
||||
<div class="grid gap-4">
|
||||
<!-- Add Expense Card -->
|
||||
<div>
|
||||
<button
|
||||
class="flex items-start gap-4 p-5 rounded-xl border bg-card text-left transition-colors hover:bg-accent/50 active:bg-accent/70"
|
||||
@click="showAddExpense = true"
|
||||
class="w-full flex items-start gap-4 p-5 rounded-xl border bg-card text-left transition-colors"
|
||||
:class="expenseDisabled
|
||||
? 'opacity-60 cursor-not-allowed'
|
||||
: 'hover:bg-accent/50 active:bg-accent/70'"
|
||||
:disabled="expenseDisabled"
|
||||
@click="!expenseDisabled && (showAddExpense = true)"
|
||||
>
|
||||
<div class="flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/20 shrink-0">
|
||||
<DollarSign class="w-6 h-6 text-red-600 dark:text-red-400" />
|
||||
<div
|
||||
class="flex items-center justify-center w-12 h-12 rounded-full shrink-0"
|
||||
:class="expenseDisabled ? 'bg-muted' : 'bg-red-100 dark:bg-red-900/20'"
|
||||
>
|
||||
<DollarSign
|
||||
class="w-6 h-6"
|
||||
:class="expenseDisabled ? 'text-muted-foreground' : 'text-red-600 dark:text-red-400'"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-lg font-semibold text-foreground">{{ t('libra.record.addExpense') }}</h2>
|
||||
<p class="text-sm text-muted-foreground mt-0.5">{{ t('libra.record.addExpenseDescription') }}</p>
|
||||
</div>
|
||||
</button>
|
||||
<div v-if="expenseDisabled" class="mt-2 flex items-start gap-1.5 px-1">
|
||||
<Lock class="w-3 h-3 mt-0.5 shrink-0 text-muted-foreground" />
|
||||
<p class="text-[11px] leading-snug text-muted-foreground">
|
||||
You don't have permission to submit expenses. Contact your administrator to request access.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Income Card -->
|
||||
<div>
|
||||
<button
|
||||
class="flex items-start gap-4 p-5 rounded-xl border bg-card text-left transition-colors hover:bg-accent/50 active:bg-accent/70"
|
||||
@click="showAddIncome = true"
|
||||
class="w-full flex items-start gap-4 p-5 rounded-xl border bg-card text-left transition-colors"
|
||||
:class="incomeDisabled
|
||||
? 'opacity-60 cursor-not-allowed'
|
||||
: 'hover:bg-accent/50 active:bg-accent/70'"
|
||||
:disabled="incomeDisabled"
|
||||
@click="!incomeDisabled && (showAddIncome = true)"
|
||||
>
|
||||
<div class="flex items-center justify-center w-12 h-12 rounded-full bg-green-100 dark:bg-green-900/20 shrink-0">
|
||||
<TrendingUp class="w-6 h-6 text-green-600 dark:text-green-400" />
|
||||
<div
|
||||
class="flex items-center justify-center w-12 h-12 rounded-full shrink-0"
|
||||
:class="incomeDisabled ? 'bg-muted' : 'bg-green-100 dark:bg-green-900/20'"
|
||||
>
|
||||
<TrendingUp
|
||||
class="w-6 h-6"
|
||||
:class="incomeDisabled ? 'text-muted-foreground' : 'text-green-600 dark:text-green-400'"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h2 class="text-lg font-semibold text-foreground">{{ t('libra.record.addIncome') }}</h2>
|
||||
<p class="text-sm text-muted-foreground mt-0.5">{{ t('libra.record.addIncomeDescription') }}</p>
|
||||
</div>
|
||||
</button>
|
||||
<div v-if="incomeDisabled" class="mt-2 flex items-start gap-1.5 px-1">
|
||||
<Lock class="w-3 h-3 mt-0.5 shrink-0 text-muted-foreground" />
|
||||
<p class="text-[11px] leading-snug text-muted-foreground">
|
||||
You don't have permission to record income. Contact your administrator to request access.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drafts Section -->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue