Files
stream.ui/src/routes/settings/Billing/Billing.vue
claude cc3f62a6a1 refactor: reorganize proto clients and settings UI
Move generated proto imports under the new server api path and align gRPC auth/client usage with the renamed clients. Polish settings UI details by adding a shared language icon and refining Ads VAST table presentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 14:06:51 +00:00

108 lines
3.6 KiB
Vue

<script setup lang="ts">
import CoinsIcon from '@/components/icons/CoinsIcon.vue';
import PlusIcon from '@/components/icons/PlusIcon.vue';
import AppButton from '@/components/ui/AppButton.vue';
import { useUsageQuery } from '@/composables/useUsageQuery';
import BillingTopupDialog from '@/routes/settings/Billing/components/BillingTopupDialog.vue';
import BillingUsageSection from '@/routes/settings/Billing/components/BillingUsageSection.vue';
import SettingsSectionCard from '@/routes/settings/components/SettingsSectionCard.vue';
import type { Plan as ModelPlan } from '@/server/api/proto/app/v1/common';
import { useAuthStore } from '@/stores/auth';
import { computed, ref } from 'vue';
import SettingsRow from '../components/SettingsRow.vue';
import PaymentHistory from './components/PaymentHistory';
import PlanSelection from './components/PlanSelection';
import UpgradePlan from './components/UpgradePlan';
const auth = useAuthStore();
const { refetch: refetchUsage } = useUsageQuery();
const topupDialogVisible = ref(false);
const topupAmount = ref<number | null>(null);
const upgradeDialogVisible = ref(false);
const selectedPlan = ref<ModelPlan | null>(null);
const currentPlanId = computed(() => auth.user?.plan_id || undefined);
const selectedPlanId = computed(() => (upgradeDialogVisible.value ? selectedPlan.value?.id || '' : ''));
const walletBalance = computed(() => auth.user?.wallet_balance || 0);
const refreshBillingState = async () => {
await Promise.allSettled([
auth.fetchMe(),
refetchUsage(),
]);
};
const openUpgradeDialog = (plan: ModelPlan) => {
selectedPlan.value = plan;
upgradeDialogVisible.value = true;
};
const closeUpgradeDialog = () => {
upgradeDialogVisible.value = false;
selectedPlan.value = null;
};
const handleUpgradeVisibilityChange = (visible: boolean) => {
upgradeDialogVisible.value = visible;
if (!visible) {
selectedPlan.value = null;
}
};
const handleUpgradeSuccess = async () => {
await refreshBillingState();
};
const openTopupDialog = () => {
topupAmount.value = null;
topupDialogVisible.value = true;
};
</script>
<template>
<SettingsSectionCard :title="$t('settings.content.billing.title')"
:description="$t('settings.content.billing.subtitle')">
<SettingsRow :title="$t('settings.billing.walletBalance')"
:description="$t('settings.billing.currentBalance', { balance: auth.formatMoney(walletBalance) })"
iconBoxClass="bg-primary/10">
<template #icon>
<CoinsIcon filled class="w-5 h-5 text-primary" />
</template>
<template #actions>
<div class="flex flex-col items-end gap-2">
<AppButton size="sm" @click="openTopupDialog">
<template #icon>
<PlusIcon class="w-4 h-4" />
</template>
{{ $t('settings.billing.topUp') }}
</AppButton>
</div>
</template>
</SettingsRow>
<PlanSelection
:current-plan-id="currentPlanId"
:selected-plan-id="selectedPlanId"
@upgrade="openUpgradeDialog"
/>
<BillingUsageSection />
<PaymentHistory />
</SettingsSectionCard>
<BillingTopupDialog v-model="topupDialogVisible" />
<UpgradePlan
:visible="upgradeDialogVisible"
:selected-plan="selectedPlan"
@update:visible="handleUpgradeVisibilityChange"
@close="closeUpgradeDialog"
@success="handleUpgradeSuccess"
/>
</template>