feat: add space account page
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bdd05b0315
commit
d747454f4a
8 changed files with 169 additions and 2 deletions
|
|
@ -146,6 +146,7 @@ func (h *spaceHandler) SpaceOverviewPage(w http.ResponseWriter, r *http.Request)
|
||||||
accountCards := make([]blocks.AccountCardInfo, 0, len(accounts))
|
accountCards := make([]blocks.AccountCardInfo, 0, len(accounts))
|
||||||
for _, a := range accounts {
|
for _, a := range accounts {
|
||||||
accountCards = append(accountCards, blocks.AccountCardInfo{
|
accountCards = append(accountCards, blocks.AccountCardInfo{
|
||||||
|
SpaceID: space.ID,
|
||||||
ID: a.ID,
|
ID: a.ID,
|
||||||
Name: a.Name,
|
Name: a.Name,
|
||||||
Balance: a.Balance,
|
Balance: a.Balance,
|
||||||
|
|
@ -158,3 +159,17 @@ func (h *spaceHandler) SpaceOverviewPage(w http.ResponseWriter, r *http.Request)
|
||||||
Accounts: accountCards,
|
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),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,10 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
spaceAccessMw := middleware.RequireSpaceAccess(a.SpaceService)
|
spaceAccessMw := middleware.RequireSpaceAccess(a.SpaceService)
|
||||||
g.Use(spaceAccessMw)
|
g.Use(spaceAccessMw)
|
||||||
g.Get("/overview", spaceH.SpaceOverviewPage).Name("page.app.spaces.space.overview")
|
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")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,17 @@ package blocks
|
||||||
import "github.com/shopspring/decimal"
|
import "github.com/shopspring/decimal"
|
||||||
import "strings"
|
import "strings"
|
||||||
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon"
|
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon"
|
||||||
|
import "git.juancwu.dev/juancwu/budgit/internal/routeurl"
|
||||||
|
|
||||||
type AccountCardInfo struct {
|
type AccountCardInfo struct {
|
||||||
|
SpaceID string
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Balance decimal.Decimal
|
Balance decimal.Decimal
|
||||||
}
|
}
|
||||||
|
|
||||||
templ AccountCard(info AccountCardInfo) {
|
templ AccountCard(info AccountCardInfo) {
|
||||||
<div class="px-2 py-2 block rounded-md hover:bg-sidebar-accent">
|
<a href={ routeurl.URL("page.app.spaces.space.accounts.account.overview", "spaceID", info.SpaceID, "accountID", info.ID) } class="px-2 py-2 block rounded-md hover:bg-sidebar-accent">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex gap-4 items-center">
|
<div class="flex gap-4 items-center">
|
||||||
<div class="w-10 h-10 shrink-0 overflow-hidden rounded-md bg-muted flex items-center justify-center">
|
<div class="w-10 h-10 shrink-0 overflow-hidden rounded-md bg-muted flex items-center justify-center">
|
||||||
|
|
@ -28,5 +30,5 @@ templ AccountCard(info AccountCardInfo) {
|
||||||
@icon.ChevronRight()
|
@icon.ChevronRight()
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
99
internal/ui/pages/space_account.templ
Normal file
99
internal/ui/pages/space_account.templ
Normal file
|
|
@ -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)) {
|
||||||
|
<div class="container px-6 py-8 mx-auto space-y-8">
|
||||||
|
<div class="grid gap-4 grid-cols-1 md:grid-cols-12">
|
||||||
|
@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() {
|
||||||
|
<h1 class={ utils.TwMerge(balanceTextClasses...) }>${ utils.FormatDecimalWithThousands(props.AccountBalance.StringFixedBank(2)) }</h1>
|
||||||
|
<p class="text-sm text-muted-foreground">Available Balance</p>
|
||||||
|
<p class="mt-8 text-sm text-muted-foreground">
|
||||||
|
Account <span>•••• { props.AccountNumber }</span>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@card.Card() {
|
||||||
|
@card.Header() {
|
||||||
|
<div>
|
||||||
|
@card.Title() {
|
||||||
|
Transaction History
|
||||||
|
}
|
||||||
|
@card.Description() {
|
||||||
|
Overview of your activity for August 2024
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@card.Content() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
4
internal/ui/pages/space_account_settings.templ
Normal file
4
internal/ui/pages/space_account_settings.templ
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
package pages
|
||||||
|
|
||||||
|
templ SpaceAccountSettings() {
|
||||||
|
}
|
||||||
4
internal/ui/pages/space_create_bill.templ
Normal file
4
internal/ui/pages/space_create_bill.templ
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
package pages
|
||||||
|
|
||||||
|
templ SpaceCreateBill() {
|
||||||
|
}
|
||||||
4
internal/ui/pages/space_create_deposit.templ
Normal file
4
internal/ui/pages/space_create_deposit.templ
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
package pages
|
||||||
|
|
||||||
|
templ SpaceCreateDeposit() {
|
||||||
|
}
|
||||||
35
internal/ui/utils/currency.go
Normal file
35
internal/ui/utils/currency.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue