From d747454f4ad5dd96f5d104755178f703eed93dcf Mon Sep 17 00:00:00 2001 From: juancwu Date: Wed, 22 Apr 2026 03:44:04 +0000 Subject: [PATCH] feat: add space account page Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/handler/space.go | 15 +++ internal/routes/routes.go | 4 + internal/ui/blocks/account_card.templ | 6 +- internal/ui/pages/space_account.templ | 99 +++++++++++++++++++ .../ui/pages/space_account_settings.templ | 4 + internal/ui/pages/space_create_bill.templ | 4 + internal/ui/pages/space_create_deposit.templ | 4 + internal/ui/utils/currency.go | 35 +++++++ 8 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 internal/ui/pages/space_account.templ create mode 100644 internal/ui/pages/space_account_settings.templ create mode 100644 internal/ui/pages/space_create_bill.templ create mode 100644 internal/ui/pages/space_create_deposit.templ create mode 100644 internal/ui/utils/currency.go diff --git a/internal/handler/space.go b/internal/handler/space.go index 9d75cab..b0b519a 100644 --- a/internal/handler/space.go +++ b/internal/handler/space.go @@ -146,6 +146,7 @@ func (h *spaceHandler) SpaceOverviewPage(w http.ResponseWriter, r *http.Request) accountCards := make([]blocks.AccountCardInfo, 0, len(accounts)) for _, a := range accounts { accountCards = append(accountCards, blocks.AccountCardInfo{ + SpaceID: space.ID, ID: a.ID, Name: a.Name, Balance: a.Balance, @@ -158,3 +159,17 @@ func (h *spaceHandler) SpaceOverviewPage(w http.ResponseWriter, r *http.Request) Accounts: accountCards, })) } + +func (h *spaceHandler) SpaceAccountPage(w http.ResponseWriter, r *http.Request) { + spaceID := r.PathValue("spaceID") + accountID := r.PathValue("accountID") + + ui.Render(w, r, pages.SpaceAccountPage(pages.SpaceAccountPageProps{ + SpaceID: spaceID, + AccountID: accountID, + AccountName: "Money Account", + AccountDescription: "Vault Infinite Priority", + AccountNumber: "4492", + AccountBalance: decimal.NewFromFloat(32093.11), + })) +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index b119788..4855051 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -92,6 +92,10 @@ func SetupRoutes(a *app.App) http.Handler { spaceAccessMw := middleware.RequireSpaceAccess(a.SpaceService) g.Use(spaceAccessMw) g.Get("/overview", spaceH.SpaceOverviewPage).Name("page.app.spaces.space.overview") + + g.SubGroup("/accounts/{accountID}", func(g *router.Group) { + g.Get("/overview", spaceH.SpaceAccountPage).Name("page.app.spaces.space.accounts.account.overview") + }) }) }) diff --git a/internal/ui/blocks/account_card.templ b/internal/ui/blocks/account_card.templ index c9a8ca3..b0b64b9 100644 --- a/internal/ui/blocks/account_card.templ +++ b/internal/ui/blocks/account_card.templ @@ -3,15 +3,17 @@ package blocks import "github.com/shopspring/decimal" import "strings" import "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon" +import "git.juancwu.dev/juancwu/budgit/internal/routeurl" type AccountCardInfo struct { + SpaceID string ID string Name string Balance decimal.Decimal } templ AccountCard(info AccountCardInfo) { -
+
@@ -28,5 +30,5 @@ templ AccountCard(info AccountCardInfo) { @icon.ChevronRight()
-
+
} diff --git a/internal/ui/pages/space_account.templ b/internal/ui/pages/space_account.templ new file mode 100644 index 0000000..7dc9d65 --- /dev/null +++ b/internal/ui/pages/space_account.templ @@ -0,0 +1,99 @@ +package pages + +import "github.com/shopspring/decimal" +import "git.juancwu.dev/juancwu/budgit/internal/ui/layouts" +import "git.juancwu.dev/juancwu/budgit/internal/ui/components/card" +import "git.juancwu.dev/juancwu/budgit/internal/ui/components/button" +import "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon" +import "git.juancwu.dev/juancwu/budgit/internal/ui/utils" + +type SpaceAccountPageProps struct { + SpaceID string + AccountID string + AccountName string + AccountDescription string + AccountNumber string + AccountBalance decimal.Decimal +} + +templ SpaceAccountPage(props SpaceAccountPageProps) { + {{ + balanceTextClasses := []string{"text-4xl font-bold"} + if props.AccountBalance.IsPositive() { + balanceTextClasses = append(balanceTextClasses, "text-green-600 dark:text-green-400") + } else { + balanceTextClasses = append(balanceTextClasses, "text-red-600 dark:text-red-400") + } + }} + @layouts.App("Space Account", spaceOverviewSidebarContent(), spaceSpecificSidebarContent(props.SpaceID)) { +
+
+ @card.Card(card.Props{ + Class: "rounded-sm col-span-full md:col-span-8", + }) { + @card.Header() { + @card.Title() { + { props.AccountName } + } + @card.Description(card.DescriptionProps{Class: "text-sm"}) { + { props.AccountDescription } + } + } + @card.Content() { +

${ utils.FormatDecimalWithThousands(props.AccountBalance.StringFixedBank(2)) }

+

Available Balance

+

+ Account •••• { props.AccountNumber } +

+ } + } + @card.Card(card.Props{Class: "rounded-sm col-span-full md:col-span-4"}) { + @card.Header() { + @card.Title() { + Quick Actions + } + } + @card.Content(card.ContentProps{Class: "space-y-4"}) { + @button.Button(button.Props{ + Class: "w-full flex gap-2 md:gap-4 items-center", + Variant: button.VariantDefault, + }) { + Pay Bills + @icon.HandCoins() + } + @button.Button(button.Props{ + Class: "w-full flex gap-2 md:gap-4 items-center", + Variant: button.VariantSecondary, + }) { + Deposit Funds + @icon.BanknoteArrowDown() + } + @button.Button(button.Props{ + Class: "w-full flex gap-2 md:gap-4 items-center", + Variant: button.VariantLink, + }) { + Account Settings + @icon.Settings() + } + } + } +
+
+ @card.Card() { + @card.Header() { +
+ @card.Title() { + Transaction History + } + @card.Description() { + Overview of your activity for August 2024 + } +
+ } + @card.Content() { + } + } +
+
+ } +} diff --git a/internal/ui/pages/space_account_settings.templ b/internal/ui/pages/space_account_settings.templ new file mode 100644 index 0000000..50f5fd3 --- /dev/null +++ b/internal/ui/pages/space_account_settings.templ @@ -0,0 +1,4 @@ +package pages + +templ SpaceAccountSettings() { +} diff --git a/internal/ui/pages/space_create_bill.templ b/internal/ui/pages/space_create_bill.templ new file mode 100644 index 0000000..ef342fc --- /dev/null +++ b/internal/ui/pages/space_create_bill.templ @@ -0,0 +1,4 @@ +package pages + +templ SpaceCreateBill() { +} diff --git a/internal/ui/pages/space_create_deposit.templ b/internal/ui/pages/space_create_deposit.templ new file mode 100644 index 0000000..a4d8ce0 --- /dev/null +++ b/internal/ui/pages/space_create_deposit.templ @@ -0,0 +1,4 @@ +package pages + +templ SpaceCreateDeposit() { +} diff --git a/internal/ui/utils/currency.go b/internal/ui/utils/currency.go new file mode 100644 index 0000000..96882df --- /dev/null +++ b/internal/ui/utils/currency.go @@ -0,0 +1,35 @@ +package utils + +import "strings" + +func FormatDecimalWithThousands(numStr string) (string, error) { + // Split into integer and decimal parts + parts := strings.SplitN(numStr, ".", 2) + intPart := parts[0] + + // Handle negative numbers + negative := false + if strings.HasPrefix(intPart, "-") { + negative = true + intPart = intPart[1:] + } + + // Insert thousand separators + var result []byte + for i, c := range intPart { + if i > 0 && (len(intPart)-i)%3 == 0 { + result = append(result, ',') + } + result = append(result, byte(c)) + } + + // Reassemble + formatted := string(result) + if len(parts) == 2 { + formatted += "." + parts[1] + } + if negative { + formatted = "-" + formatted + } + return formatted, nil +}