From 145eed9eef2c334cc838c3d61319afda7e6a415f Mon Sep 17 00:00:00 2001 From: juancwu Date: Sun, 3 May 2026 23:02:51 +0000 Subject: [PATCH] feat: new home page --- internal/handler/home.go | 2 +- internal/handler/redirect.go | 2 +- internal/handler/redirect_test.go | 2 +- internal/handler/space.go | 36 ++++++++++++ internal/routes/routes.go | 2 + internal/ui/pages/home.templ | 91 +++++++++++++++++++++++++++++++ internal/ui/pages/spaces.templ | 10 ++++ 7 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 internal/ui/pages/home.templ diff --git a/internal/handler/home.go b/internal/handler/home.go index 431ddb9..296b45d 100644 --- a/internal/handler/home.go +++ b/internal/handler/home.go @@ -21,7 +21,7 @@ func (h *homeHandler) HomePage(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/auth", http.StatusSeeOther) return } - http.Redirect(w, r, "/app/spaces", http.StatusSeeOther) + http.Redirect(w, r, "/app/home", http.StatusSeeOther) } func (h *homeHandler) PrivacyPage(w http.ResponseWriter, r *http.Request) { diff --git a/internal/handler/redirect.go b/internal/handler/redirect.go index 7c47547..d391463 100644 --- a/internal/handler/redirect.go +++ b/internal/handler/redirect.go @@ -9,5 +9,5 @@ func NewRedirectHandler() *redirectHandler { } func (h *redirectHandler) Spaces(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/app/spaces", http.StatusMovedPermanently) + http.Redirect(w, r, "/app/home", http.StatusMovedPermanently) } diff --git a/internal/handler/redirect_test.go b/internal/handler/redirect_test.go index c1a5d0a..297bd2c 100644 --- a/internal/handler/redirect_test.go +++ b/internal/handler/redirect_test.go @@ -17,5 +17,5 @@ func TestRedirectHandler_RederictToSpaces(t *testing.T) { h.Spaces(w, req) assert.Equal(t, http.StatusMovedPermanently, w.Code) - assert.Equal(t, "/app/spaces", w.Header().Get("Location")) + assert.Equal(t, "/app/home", w.Header().Get("Location")) } diff --git a/internal/handler/space.go b/internal/handler/space.go index 192ca67..e71e401 100644 --- a/internal/handler/space.go +++ b/internal/handler/space.go @@ -41,6 +41,42 @@ func NewSpaceHandler( } } +func (h *spaceHandler) HomePage(w http.ResponseWriter, r *http.Request) { + user := ctxkeys.User(r.Context()) + if user == nil { + ui.RenderError(w, r, "Unauthorized", http.StatusUnauthorized) + return + } + + owned, err := h.spaceService.GetOwnedSpaces(user.ID) + if err != nil { + slog.Error("failed to load owned spaces", "error", err, "user_id", user.ID) + ui.RenderError(w, r, "Failed to load spaces", http.StatusInternalServerError) + return + } + + shared, err := h.spaceService.GetSharedSpaces(user.ID) + if err != nil { + slog.Error("failed to load shared spaces", "error", err, "user_id", user.ID) + ui.RenderError(w, r, "Failed to load spaces", http.StatusInternalServerError) + return + } + + ownedCards := h.buildSpaceCards(owned) + sharedCards := h.buildSpaceCards(shared) + + total := decimal.Zero + for _, c := range ownedCards { + total = total.Add(c.TotalBalance) + } + + ui.Render(w, r, pages.Home(pages.HomeProps{ + OwnedSpaces: ownedCards, + SharedSpaces: sharedCards, + TotalBalance: total, + })) +} + func (h *spaceHandler) SpacesPage(w http.ResponseWriter, r *http.Request) { user := ctxkeys.User(r.Context()) if user == nil { diff --git a/internal/routes/routes.go b/internal/routes/routes.go index a02fccd..0b90909 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -85,6 +85,8 @@ func SetupRoutes(a *app.App) http.Handler { r.Group("/app", func(g *router.Group) { g.Use(middleware.RequireAuth) + g.Get("/home", spaceH.HomePage).Name("page.app.home") + g.SubGroup("/spaces", func(g *router.Group) { g.Get("", spaceH.SpacesPage).Name("page.app.spaces") g.Get("/create", spaceH.CreateSpacePage).Name("page.app.spaces.create") diff --git a/internal/ui/pages/home.templ b/internal/ui/pages/home.templ new file mode 100644 index 0000000..54ec359 --- /dev/null +++ b/internal/ui/pages/home.templ @@ -0,0 +1,91 @@ +package pages + +import ( + "fmt" + + "git.juancwu.dev/juancwu/budgit/internal/ctxkeys" + "git.juancwu.dev/juancwu/budgit/internal/routeurl" + "git.juancwu.dev/juancwu/budgit/internal/ui/blocks" + "git.juancwu.dev/juancwu/budgit/internal/ui/components/button" + "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon" + "git.juancwu.dev/juancwu/budgit/internal/ui/layouts" + "git.juancwu.dev/juancwu/budgit/internal/ui/utils" + "github.com/shopspring/decimal" +) + +type HomeProps struct { + OwnedSpaces []blocks.SpaceCardInfo + SharedSpaces []blocks.SpaceCardInfo + TotalBalance decimal.Decimal +} + +templ Home(props HomeProps) { + {{ + user := ctxkeys.User(ctx) + displayName := "" + if user != nil && user.Name != nil { + displayName = *user.Name + } + totalSpaces := len(props.OwnedSpaces) + len(props.SharedSpaces) + }} + @layouts.App("Home", spaceOverviewSidebarContent()) { +
+
+
+

Home

+

Hello, { displayName }

+ if totalSpaces == 0 { +

Create a space to start tracking your expenses.

+ } else { + {{ + countPhrase := fmt.Sprintf("Across %d spaces", totalSpaces) + if totalSpaces == 1 { + countPhrase = "Across 1 space" + } + }} +

{ countPhrase } you own you have ${ utils.FormatDecimalWithThousands(props.TotalBalance.StringFixedBank(2)) } tracked.

+ } +
+
+ @button.Button(button.Props{ + Class: "flex gap-2 items-center", + Href: routeurl.URL("page.app.spaces.create"), + }) { + @icon.Plus() + Create space + } +
+
+
+
+

My spaces

+ { fmt.Sprintf("%d", len(props.OwnedSpaces)) } +
+ if len(props.OwnedSpaces) == 0 { +

You don't own any spaces yet.

+ } else { +
+ for _, space := range props.OwnedSpaces { + @blocks.SpaceCard(space) + } +
+ } +
+
+
+

Shared with me

+ { fmt.Sprintf("%d", len(props.SharedSpaces)) } +
+ if len(props.SharedSpaces) == 0 { +

No spaces have been shared with you.

+ } else { +
+ for _, space := range props.SharedSpaces { + @blocks.SpaceCard(space) + } +
+ } +
+
+ } +} diff --git a/internal/ui/pages/spaces.templ b/internal/ui/pages/spaces.templ index 4331fbc..5148575 100644 --- a/internal/ui/pages/spaces.templ +++ b/internal/ui/pages/spaces.templ @@ -81,6 +81,16 @@ templ spaceOverviewSidebarContent() { Overview } @sidebar.Menu() { + @sidebar.MenuItem() { + @sidebar.MenuButton(sidebar.MenuButtonProps{ + Href: routeurl.URL("page.app.home"), + IsActive: ctxkeys.URLPath(ctx) == routeurl.URL("page.app.home"), + Tooltip: "Home", + }) { + @icon.House() + Home + } + } @sidebar.MenuItem() { @sidebar.MenuButton(sidebar.MenuButtonProps{ Href: routeurl.URL("page.app.spaces"),