diff --git a/frontend/src/components/AccountsOverview.tsx b/frontend/src/components/AccountsOverview.tsx index 07ea005..d0112d1 100644 --- a/frontend/src/components/AccountsOverview.tsx +++ b/frontend/src/components/AccountsOverview.tsx @@ -23,6 +23,7 @@ import { import { Button } from "./ui/button"; import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; import AccountsSkeleton from "./AccountsSkeleton"; +import { BlurredValue } from "./ui/blurred-value"; import type { Account, Balance } from "../types/api"; // Helper function to get status indicator color and styles @@ -158,7 +159,7 @@ export default function AccountsOverview() { Total Balance

- {formatCurrency(totalBalance)} + {formatCurrency(totalBalance)}

@@ -369,7 +370,9 @@ export default function AccountsOverview() { isPositive ? "text-green-600" : "text-red-600" }`} > - {formatCurrency(balance, currency)} + + {formatCurrency(balance, currency)} +

diff --git a/frontend/src/components/AppSidebar.tsx b/frontend/src/components/AppSidebar.tsx index ef436c0..ce512d6 100644 --- a/frontend/src/components/AppSidebar.tsx +++ b/frontend/src/components/AppSidebar.tsx @@ -16,6 +16,7 @@ import { apiClient } from "../lib/api"; import { formatCurrency } from "../lib/utils"; import { useState } from "react"; import type { Account } from "../types/api"; +import { BlurredValue } from "./ui/blurred-value"; import { Sidebar, SidebarContent, @@ -130,7 +131,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) {

- {formatCurrency(totalBalance)} + {formatCurrency(totalBalance)}

{accounts?.length || 0} accounts @@ -163,7 +164,9 @@ export function AppSidebar({ ...props }: React.ComponentProps) { "Unnamed Account"}

- {formatCurrency(primaryBalance, currency)} + + {formatCurrency(primaryBalance, currency)} +

diff --git a/frontend/src/components/SiteHeader.tsx b/frontend/src/components/SiteHeader.tsx index 68b7e11..2c89f75 100644 --- a/frontend/src/components/SiteHeader.tsx +++ b/frontend/src/components/SiteHeader.tsx @@ -3,6 +3,7 @@ import { Activity, Wifi, WifiOff } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import { apiClient } from "../lib/api"; import { ThemeToggle } from "./ui/theme-toggle"; +import { BalanceToggle } from "./ui/balance-toggle"; import { Separator } from "./ui/separator"; import { SidebarTrigger } from "./ui/sidebar"; @@ -77,6 +78,7 @@ export function SiteHeader() { )} + diff --git a/frontend/src/components/TransactionsTable.tsx b/frontend/src/components/TransactionsTable.tsx index 1feb559..9fd50f4 100644 --- a/frontend/src/components/TransactionsTable.tsx +++ b/frontend/src/components/TransactionsTable.tsx @@ -31,7 +31,8 @@ import { DataTablePagination } from "./ui/data-table-pagination"; import { Card } from "./ui/card"; import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; import { Button } from "./ui/button"; -import type { Account, Transaction, ApiResponse } from "../types/api"; +import { BlurredValue } from "./ui/blurred-value"; +import type { Account, Transaction, PaginatedResponse } from "../types/api"; export default function TransactionsTable() { // Filter state consolidated into a single object @@ -102,7 +103,7 @@ export default function TransactionsTable() { isLoading: transactionsLoading, error: transactionsError, refetch: refetchTransactions, - } = useQuery>({ + } = useQuery>({ queryKey: [ "transactions", filterState.selectedAccount, @@ -125,7 +126,16 @@ export default function TransactionsTable() { }); const transactions = transactionsResponse?.data || []; - const pagination = transactionsResponse?.pagination; + const pagination = transactionsResponse + ? { + page: transactionsResponse.page, + total_pages: transactionsResponse.total_pages, + per_page: transactionsResponse.per_page, + total: transactionsResponse.total, + has_next: transactionsResponse.has_next, + has_prev: transactionsResponse.has_prev, + } + : undefined; // Check if search is currently debouncing const isSearchLoading = filterState.searchTerm !== debouncedSearchTerm; @@ -221,11 +231,13 @@ export default function TransactionsTable() { isPositive ? "text-green-600" : "text-red-600" }`} > - {isPositive ? "+" : ""} - {formatCurrency( - transaction.transaction_value, - transaction.transaction_currency, - )} + + {isPositive ? "+" : ""} + {formatCurrency( + transaction.transaction_value, + transaction.transaction_currency, + )} +

); @@ -525,11 +537,13 @@ export default function TransactionsTable() { isPositive ? "text-green-600" : "text-red-600" }`} > - {isPositive ? "+" : ""} - {formatCurrency( - transaction.transaction_value, - transaction.transaction_currency, - )} + + {isPositive ? "+" : ""} + {formatCurrency( + transaction.transaction_value, + transaction.transaction_currency, + )} +

+ ); +} diff --git a/frontend/src/components/ui/blurred-value.tsx b/frontend/src/components/ui/blurred-value.tsx new file mode 100644 index 0000000..cebc6f9 --- /dev/null +++ b/frontend/src/components/ui/blurred-value.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { useBalanceVisibility } from "../../contexts/BalanceVisibilityContext"; +import { cn } from "../../lib/utils"; + +interface BlurredValueProps { + children: React.ReactNode; + className?: string; +} + +export function BlurredValue({ children, className }: BlurredValueProps) { + const { isBalanceVisible } = useBalanceVisibility(); + + return ( + + {children} + + ); +} diff --git a/frontend/src/contexts/BalanceVisibilityContext.tsx b/frontend/src/contexts/BalanceVisibilityContext.tsx new file mode 100644 index 0000000..fad0408 --- /dev/null +++ b/frontend/src/contexts/BalanceVisibilityContext.tsx @@ -0,0 +1,48 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; + +interface BalanceVisibilityContextType { + isBalanceVisible: boolean; + toggleBalanceVisibility: () => void; +} + +const BalanceVisibilityContext = createContext< + BalanceVisibilityContextType | undefined +>(undefined); + +export function BalanceVisibilityProvider({ + children, +}: { + children: React.ReactNode; +}) { + const [isBalanceVisible, setIsBalanceVisible] = useState(() => { + const stored = localStorage.getItem("balanceVisible"); + // Default to true (visible) if not set + return stored === null ? true : stored === "true"; + }); + + useEffect(() => { + localStorage.setItem("balanceVisible", String(isBalanceVisible)); + }, [isBalanceVisible]); + + const toggleBalanceVisibility = () => { + setIsBalanceVisible((prev) => !prev); + }; + + return ( + + {children} + + ); +} + +export function useBalanceVisibility() { + const context = useContext(BalanceVisibilityContext); + if (context === undefined) { + throw new Error( + "useBalanceVisibility must be used within a BalanceVisibilityProvider", + ); + } + return context; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d991913..d81cb7e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client"; import { createRouter, RouterProvider } from "@tanstack/react-router"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ThemeProvider } from "./contexts/ThemeContext"; +import { BalanceVisibilityProvider } from "./contexts/BalanceVisibilityContext"; import "./index.css"; import { routeTree } from "./routeTree.gen"; import { registerSW } from "virtual:pwa-register"; @@ -73,7 +74,9 @@ createRoot(document.getElementById("root")!).render( - + + + , diff --git a/frontend/src/routes/analytics.tsx b/frontend/src/routes/analytics.tsx index 9ef84ba..5647349 100644 --- a/frontend/src/routes/analytics.tsx +++ b/frontend/src/routes/analytics.tsx @@ -88,6 +88,7 @@ function AnalyticsDashboard() { subtitle="Inflows this period" icon={TrendingUp} iconColor="green" + shouldBlur={true} /> @@ -106,6 +108,7 @@ function AnalyticsDashboard() { subtitle="Income minus expenses" icon={CreditCard} iconColor={(stats?.net_change || 0) >= 0 ? "green" : "red"} + shouldBlur={true} />