From 73f5df9647a507b54205ab54f094b066c2ea63fb Mon Sep 17 00:00:00 2001 From: kassem Date: Mon, 20 Oct 2025 14:02:55 +0300 Subject: [PATCH 1/6] refactor: replace pending records with new transfer records system --- .../src/components/DashboardSidebar.vue | 5 - .../dashboard/AdminPendingRecordsCard.vue | 19 ++-- .../dashboard/PendingRecordsTable.vue | 101 ------------------ .../dashboard/TransferRecordsTable.vue | 94 ++++++++++++++++ .../dashboard/UserPendingRecordsCard.vue | 33 ------ frontend/kubecloud/src/router/index.ts | 22 ++-- frontend/kubecloud/src/utils/adminService.ts | 26 +++-- frontend/kubecloud/src/utils/userService.ts | 51 --------- .../src/views/PendingRecordsView.vue | 95 ---------------- .../kubecloud/src/views/UserDashboard.vue | 4 +- 10 files changed, 131 insertions(+), 319 deletions(-) delete mode 100644 frontend/kubecloud/src/components/dashboard/PendingRecordsTable.vue create mode 100644 frontend/kubecloud/src/components/dashboard/TransferRecordsTable.vue delete mode 100644 frontend/kubecloud/src/components/dashboard/UserPendingRecordsCard.vue delete mode 100644 frontend/kubecloud/src/views/PendingRecordsView.vue diff --git a/frontend/kubecloud/src/components/DashboardSidebar.vue b/frontend/kubecloud/src/components/DashboardSidebar.vue index e8032ae38..347457849 100644 --- a/frontend/kubecloud/src/components/DashboardSidebar.vue +++ b/frontend/kubecloud/src/components/DashboardSidebar.vue @@ -63,11 +63,6 @@ const navigationItems = [ title: 'Billing History', icon: 'mdi-receipt' }, - { - key: 'payments', - title: 'Payments', - icon: 'mdi-clock-outline' - }, { key: 'vouchers', title: 'Vouchers', diff --git a/frontend/kubecloud/src/components/dashboard/AdminPendingRecordsCard.vue b/frontend/kubecloud/src/components/dashboard/AdminPendingRecordsCard.vue index 49ae429b7..068839485 100644 --- a/frontend/kubecloud/src/components/dashboard/AdminPendingRecordsCard.vue +++ b/frontend/kubecloud/src/components/dashboard/AdminPendingRecordsCard.vue @@ -8,9 +8,8 @@ - @@ -18,25 +17,25 @@ - - diff --git a/frontend/kubecloud/src/components/dashboard/TransferRecordsTable.vue b/frontend/kubecloud/src/components/dashboard/TransferRecordsTable.vue new file mode 100644 index 000000000..3c55a7b15 --- /dev/null +++ b/frontend/kubecloud/src/components/dashboard/TransferRecordsTable.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/frontend/kubecloud/src/components/dashboard/UserPendingRecordsCard.vue b/frontend/kubecloud/src/components/dashboard/UserPendingRecordsCard.vue deleted file mode 100644 index 158b0c1fa..000000000 --- a/frontend/kubecloud/src/components/dashboard/UserPendingRecordsCard.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/frontend/kubecloud/src/router/index.ts b/frontend/kubecloud/src/router/index.ts index 756988884..c593345b2 100644 --- a/frontend/kubecloud/src/router/index.ts +++ b/frontend/kubecloud/src/router/index.ts @@ -85,11 +85,6 @@ const router = createRouter({ component: () => import('../views/ForgotPasswordView.vue'), meta: { requiresGuest: true } }, - { - path: '/pending-requests', - name: 'pending-requests', - component: () => import('../views/PendingRecordsView.vue') - }, { path: '/maintenance', name: 'maintenance', @@ -126,39 +121,36 @@ async function handleMaintenanceCheck(to: RouteLocationNormalizedGeneric, next: return false // Continue with normal routing on error } } - if (to.path === '/maintenance' && !maintenanceStore.isMaintenanceMode) { next('/') - return true + return true } - if (maintenanceStore.isMaintenanceMode) { // Allow access to maintenance page itself if (to.path === '/maintenance') { next() - return true + return true } if (maintenanceStore.isRouteAllowed(to.path)) { next() - return true + return true } next('/maintenance') - return true + return true } - - return false + return false } router.beforeEach(async (to, _from, next) => { const userStore = useUserStore() const maintenanceStore = useMaintenanceStore() - + // Handle maintenance mode checks const maintenanceHandled = await handleMaintenanceCheck(to, next) if (maintenanceHandled) { return } - + // Check if user is authenticated const isAuthenticated = userStore.isLoggedIn diff --git a/frontend/kubecloud/src/utils/adminService.ts b/frontend/kubecloud/src/utils/adminService.ts index 1e9c30b6f..c67e623fe 100644 --- a/frontend/kubecloud/src/utils/adminService.ts +++ b/frontend/kubecloud/src/utils/adminService.ts @@ -1,7 +1,6 @@ import router from "@/router" import { api } from "./api" import type { ApiResponse } from "./authService" -import type { PendingRecord } from "./userService" // Types for admin requests and responses export interface User { @@ -75,6 +74,21 @@ export interface Invoice { created_at: string } +export type RecordOperation = 'withdraw' | 'deposit' +export type RecordState = 'pending' | 'success' | 'failed' + +export interface TransferRecord { + created_at: string, + failure: string, + id: number, + operation: RecordOperation, + state: RecordState, + tft_amount: number, + updated_at: string, + user_id: number, + username: string +} + // Admin service class export class AdminService { private static instance: AdminService @@ -151,14 +165,14 @@ export class AdminService { return response.data.data.invoices } - // List all pending records (requires admin auth) - async listPendingRecords(): Promise { - const response = await api.get>('/v1/pending-records', { + // List all transfer records (requires admin auth) + async listTransferRecords(): Promise { + const response = await api.get>('/v1/transfer-records', { requiresAuth: true, showNotifications: true, - errorMessage: 'Failed to load payments' + errorMessage: 'Failed to load transfer records' }) - return response.data.data.pending_records + return response.data.data.transfer_records } // Send a system email to all users (requires admin auth) diff --git a/frontend/kubecloud/src/utils/userService.ts b/frontend/kubecloud/src/utils/userService.ts index c8dc42291..54a75ef8e 100644 --- a/frontend/kubecloud/src/utils/userService.ts +++ b/frontend/kubecloud/src/utils/userService.ts @@ -119,18 +119,6 @@ export interface TaskResponse { created_at: string; } -export interface PendingRecord { - created_at: string; - id: number; - tft_amount: number; - transferred_tft_amount: number; - transferred_usd_amount: number; - updated_at: string; - usd_amount: number; - user_id: number; - username: string; - transfer_mode: string; -} export interface TwinResponse { public_key: string; @@ -226,26 +214,6 @@ export class UserService { ) } - // Fetch the user's current balance - async fetchBalance(): Promise<{balance: number, pending_balance: number}> { - try { - const response = await api.get<{ data: { balance_usd: number, debt_usd: number, pending_balance_usd: number } }>( - '/v1/user/balance', - { requiresAuth: true, showNotifications: false } - ) - const { balance_usd, debt_usd, pending_balance_usd } = response.data.data - return {balance: (balance_usd || 0) - (debt_usd || 0), pending_balance: pending_balance_usd || 0} - }catch(e){ - if (!(e as ApiError)?.silent) { - useNotificationStore().error( - 'Error', - 'Failed to fetch balance', - ) - } - return {balance: 0, pending_balance: 0} - } - } - // List all SSH keys for the current user async listSshKeys(): Promise { const response = await api.get>('/v1/user/ssh-keys', { @@ -283,25 +251,6 @@ export class UserService { return api.delete(`/v1/deployments/${deploymentName}/nodes/${nodeName}`, { requiresAuth: true, showNotifications: true }) } - // List all pending records - async listPendingRecords(): Promise { - const response = await api.get>('/v1/user/pending-records', { - requiresAuth: true, - showNotifications: true, - errorMessage: 'Failed to load payments' - }) - return response.data.data - } - - async listUserPendingRecords(): Promise { - const response = await api.get<{ data: { pending_records: PendingRecord[] } }>(`/v1/user/pending-records`, { - requiresAuth: true, - showNotifications: true, - errorMessage: 'Failed to load payments' - }) - return response.data.data.pending_records - } - // Fetch twin account info async getTwinAccount(twinId: number): Promise { const response = await api.get>(`/v1/twins/${twinId}/account`, { diff --git a/frontend/kubecloud/src/views/PendingRecordsView.vue b/frontend/kubecloud/src/views/PendingRecordsView.vue deleted file mode 100644 index da70066e1..000000000 --- a/frontend/kubecloud/src/views/PendingRecordsView.vue +++ /dev/null @@ -1,95 +0,0 @@ - - - - - diff --git a/frontend/kubecloud/src/views/UserDashboard.vue b/frontend/kubecloud/src/views/UserDashboard.vue index 17b81f4ef..7f0e2a91f 100644 --- a/frontend/kubecloud/src/views/UserDashboard.vue +++ b/frontend/kubecloud/src/views/UserDashboard.vue @@ -13,7 +13,6 @@ import DashboardSidebar from '../components/DashboardSidebar.vue' import { userService } from '../utils/userService' import { useClusterStore } from '../stores/clusters' import { useNotificationStore } from '../stores/notifications' -import UserPendingRecordsCard from '../components/dashboard/UserPendingRecordsCard.vue' const userStore = useUserStore() const userName = computed(() => userStore.user?.username || 'User') @@ -139,8 +138,7 @@ function handleNavigateToFund() { - - + From 28d7192be019aa6689697d056552c46c6bcf4907 Mon Sep 17 00:00:00 2001 From: kassem Date: Mon, 20 Oct 2025 14:48:15 +0300 Subject: [PATCH 2/6] feat: implement new balance calculation using credit card and credited balances --- frontend/kubecloud/src/App.vue | 3 +++ frontend/kubecloud/src/stores/user.ts | 13 ++++++++++--- frontend/kubecloud/src/views/UserDashboard.vue | 5 +---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/kubecloud/src/App.vue b/frontend/kubecloud/src/App.vue index 62b1c8a0c..7c38ca721 100644 --- a/frontend/kubecloud/src/App.vue +++ b/frontend/kubecloud/src/App.vue @@ -57,6 +57,9 @@ onMounted(async () => { return } userStore.initializeAuth() + if(userStore.isLoggedIn){ + await userStore.loadUser() + } await notificationStore.loadNotifications() } catch (error) { console.error('Failed to initialize application:', error) diff --git a/frontend/kubecloud/src/stores/user.ts b/frontend/kubecloud/src/stores/user.ts index 9b62b2c3d..08f16af3e 100644 --- a/frontend/kubecloud/src/stores/user.ts +++ b/frontend/kubecloud/src/stores/user.ts @@ -18,6 +18,10 @@ export interface User { balance_usd?: number pending_balance_usd?: number balance: number + credit_card_balance: number + credited_balance: number + credit_card_balance_in_usd: number + credited_balance_in_usd: number } export interface AuthState { @@ -26,6 +30,9 @@ export interface AuthState { isLoading: boolean error: string | null } +function calculateNetBalance(user: User) { + return user.credit_card_balance_in_usd + user.credited_balance_in_usd +} export const useUserStore = defineStore('user', // Store definition @@ -60,6 +67,8 @@ export const useUserStore = defineStore('user', const loadUser = async () => { const userRes = await api.get>('/v1/user/', { requiresAuth: true, showNotifications: false }) user.value = userRes.data.data.user + netBalance.value = calculateNetBalance(user.value) + return user.value } const login = async (email: string, password: string) => { @@ -177,9 +186,7 @@ export const useUserStore = defineStore('user', } const updateNetBalance = async () => { - const balance = await userService.fetchBalance() - netBalance.value = balance.balance - pendingBalance.value = balance.pending_balance + netBalance.value = calculateNetBalance(await loadUser() ) } return { diff --git a/frontend/kubecloud/src/views/UserDashboard.vue b/frontend/kubecloud/src/views/UserDashboard.vue index 7f0e2a91f..fdd30263e 100644 --- a/frontend/kubecloud/src/views/UserDashboard.vue +++ b/frontend/kubecloud/src/views/UserDashboard.vue @@ -52,10 +52,7 @@ onMounted(async () => { } // Fetch initial data - const [invoices] = await Promise.all([ - userService.listUserInvoices(), - userStore.updateNetBalance(), - ]) + const invoices = await userService.listUserInvoices() // Process invoices billingHistory.value = invoices.map(inv => ({ From 4ead7208d3aa0fefbc263de12d7f864a39c68b87 Mon Sep 17 00:00:00 2001 From: kassem Date: Mon, 20 Oct 2025 16:12:05 +0300 Subject: [PATCH 3/6] refactor: centralize User type definition and update balance display in admin table --- .../src/components/AdminUsersTable.vue | 36 ++++++++++++------- frontend/kubecloud/src/stores/user.ts | 26 ++------------ frontend/kubecloud/src/types/user.ts | 23 ++++++++++++ frontend/kubecloud/src/utils/adminService.ts | 12 +------ frontend/kubecloud/src/utils/dateUtils.ts | 20 +++++++---- 5 files changed, 64 insertions(+), 53 deletions(-) create mode 100644 frontend/kubecloud/src/types/user.ts diff --git a/frontend/kubecloud/src/components/AdminUsersTable.vue b/frontend/kubecloud/src/components/AdminUsersTable.vue index 314d59c47..adc4083a6 100644 --- a/frontend/kubecloud/src/components/AdminUsersTable.vue +++ b/frontend/kubecloud/src/components/AdminUsersTable.vue @@ -20,29 +20,40 @@ { title: 'ID', key: 'id', width: '80px' }, { title: 'Name', key: 'username' }, { title: 'Email', key: 'email' }, - { title: 'Balance', key: 'balance' }, + { title: 'Balance (USD)', key: 'balance_in_usd' }, + { title: 'Balance (TFT)', key: 'balance_in_tft' }, + { title: 'Admin', key: 'admin' }, { title: 'Actions', key: 'actions', sortable: false, width: '160px' } ]" :items="users" :items-per-page="pageSize" :page="currentPage" + :sort-by="[{ key: 'id', order: 'asc' }]" @update:page="$emit('update:currentPage', $event)" class="admin-table" density="comfortable" > -