feat: shared with me page
This commit is contained in:
parent
071f6c5855
commit
48cae4957e
5 changed files with 96 additions and 8 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"git.juancwu.dev/juancwu/budgit/internal/ctxkeys"
|
||||
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||
"git.juancwu.dev/juancwu/budgit/internal/service"
|
||||
"git.juancwu.dev/juancwu/budgit/internal/ui"
|
||||
"git.juancwu.dev/juancwu/budgit/internal/ui/blocks"
|
||||
|
|
@ -30,27 +31,48 @@ func (h *spaceHandler) SpacesPage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
spaces, err := h.spaceService.GetSpacesForUser(user.ID)
|
||||
spaces, err := h.spaceService.GetOwnedSpaces(user.ID)
|
||||
if err != nil {
|
||||
slog.Error("failed to load spaces", "error", err, "user_id", user.ID)
|
||||
ui.RenderError(w, r, "Failed to load spaces", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cards := h.buildSpaceCards(spaces)
|
||||
ui.Render(w, r, pages.Spaces(cards))
|
||||
}
|
||||
|
||||
func (h *spaceHandler) SharedSpacesPage(w http.ResponseWriter, r *http.Request) {
|
||||
user := ctxkeys.User(r.Context())
|
||||
if user == nil {
|
||||
ui.RenderError(w, r, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
spaces, 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 shared spaces", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cards := h.buildSpaceCards(spaces)
|
||||
ui.Render(w, r, pages.SharedSpaces(cards))
|
||||
}
|
||||
|
||||
func (h *spaceHandler) buildSpaceCards(spaces []*model.Space) []blocks.SpaceCardInfo {
|
||||
cards := make([]blocks.SpaceCardInfo, 0, len(spaces))
|
||||
for _, sp := range spaces {
|
||||
memberCount, err := h.spaceService.GetMemberCount(sp.ID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get space member count", "error", err, "space_id", sp.ID)
|
||||
memberCount = 0
|
||||
err = nil
|
||||
}
|
||||
|
||||
accounts, err := h.accountService.GetAccountsForSpace(sp.ID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get space accounts", "error", err, "space_id", sp.ID)
|
||||
accounts = nil
|
||||
err = nil
|
||||
}
|
||||
|
||||
totalBalance := decimal.Zero
|
||||
|
|
@ -65,8 +87,7 @@ func (h *spaceHandler) SpacesPage(w http.ResponseWriter, r *http.Request) {
|
|||
TotalBalance: totalBalance,
|
||||
})
|
||||
}
|
||||
|
||||
ui.Render(w, r, pages.Spaces(cards))
|
||||
return cards
|
||||
}
|
||||
|
||||
func (h *spaceHandler) CreateSpacePage(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ type SpaceRepository interface {
|
|||
Create(space *model.Space) error
|
||||
ByID(id string) (*model.Space, error)
|
||||
ByUserID(userID string) ([]*model.Space, error)
|
||||
ByOwnerID(userID string) ([]*model.Space, error)
|
||||
SharedWithUser(userID string) ([]*model.Space, error)
|
||||
AddMember(spaceID, userID string, role model.Role) error
|
||||
RemoveMember(spaceID, userID string) error
|
||||
IsMember(spaceID, userID string) (bool, error)
|
||||
|
|
@ -91,6 +93,37 @@ func (r *spaceRepository) ByUserID(userID string) ([]*model.Space, error) {
|
|||
return spaces, nil
|
||||
}
|
||||
|
||||
func (r *spaceRepository) ByOwnerID(userID string) ([]*model.Space, error) {
|
||||
var spaces []*model.Space
|
||||
query := `
|
||||
SELECT s.*
|
||||
FROM spaces s
|
||||
WHERE s.owner_id = $1
|
||||
ORDER BY s.created_at DESC;
|
||||
`
|
||||
err := r.db.Select(&spaces, query, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spaces, nil
|
||||
}
|
||||
|
||||
func (r *spaceRepository) SharedWithUser(userID string) ([]*model.Space, error) {
|
||||
var spaces []*model.Space
|
||||
query := `
|
||||
SELECT s.*
|
||||
FROM spaces s
|
||||
JOIN space_members sm ON s.id = sm.space_id
|
||||
WHERE sm.user_id = $1 AND s.owner_id != $1
|
||||
ORDER BY s.created_at DESC;
|
||||
`
|
||||
err := r.db.Select(&spaces, query, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spaces, nil
|
||||
}
|
||||
|
||||
func (r *spaceRepository) AddMember(spaceID, userID string, role model.Role) error {
|
||||
query := `INSERT INTO space_members (space_id, user_id, role, joined_at) VALUES ($1, $2, $3, $4);`
|
||||
_, err := r.db.Exec(query, spaceID, userID, role, time.Now())
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func SetupRoutes(a *app.App) http.Handler {
|
|||
})
|
||||
|
||||
g.SubGroup("/shared-with-me", func(g *router.Group) {
|
||||
g.Get("", spaceH.SpacesPage).Name("page.app.shared-with-me")
|
||||
g.Get("", spaceH.SharedSpacesPage).Name("page.app.shared-with-me")
|
||||
})
|
||||
|
||||
g.SubGroup("/settings", func(g *router.Group) {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,24 @@ func (s *SpaceService) GetSpacesForUser(userID string) ([]*model.Space, error) {
|
|||
return spaces, nil
|
||||
}
|
||||
|
||||
// GetOwnedSpaces returns spaces owned by the user.
|
||||
func (s *SpaceService) GetOwnedSpaces(userID string) ([]*model.Space, error) {
|
||||
spaces, err := s.spaceRepo.ByOwnerID(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get owned spaces: %w", err)
|
||||
}
|
||||
return spaces, nil
|
||||
}
|
||||
|
||||
// GetSharedSpaces returns spaces shared with the user (not owned by them).
|
||||
func (s *SpaceService) GetSharedSpaces(userID string) ([]*model.Space, error) {
|
||||
spaces, err := s.spaceRepo.SharedWithUser(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get shared spaces: %w", err)
|
||||
}
|
||||
return spaces, nil
|
||||
}
|
||||
|
||||
// GetSpace retrieves a single space by its ID.
|
||||
func (s *SpaceService) GetSpace(spaceID string) (*model.Space, error) {
|
||||
space, err := s.spaceRepo.ByID(spaceID)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ templ Spaces(spaces []blocks.SpaceCardInfo) {
|
|||
<div class="container px-6 py-8 mx-auto">
|
||||
<div class="mb-8 w-full flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Spaces</h1>
|
||||
<p class="text-muted-foreground mt-2">Manage and monitor your expenses across collaborative spaces.</p>
|
||||
<h1 class="text-3xl font-bold">My Spaces</h1>
|
||||
<p class="text-muted-foreground mt-2">Manage and monitor your expenses.</p>
|
||||
</div>
|
||||
<div>
|
||||
@button.Button(button.Props{
|
||||
|
|
@ -32,3 +32,19 @@ templ Spaces(spaces []blocks.SpaceCardInfo) {
|
|||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ SharedSpaces(spaces []blocks.SpaceCardInfo) {
|
||||
@layouts.App("Shared with me") {
|
||||
<div class="container px-6 py-8 mx-auto">
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold">Shared with me</h1>
|
||||
<p class="text-muted-foreground mt-2">Spaces that others have shared with you.</p>
|
||||
</div>
|
||||
<div class="">
|
||||
for _, space := range spaces {
|
||||
@blocks.SpaceCard(space)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue