Hypercode/alex/hypercodePublic

Code

  1. hypercode
  2. views
  3. pages
  4. user_profile.go
user_profile.go222 lines
package pages

import (
	"net/http"

	"github.com/hypercodehq/hypercode/database/models"
	"github.com/hypercodehq/libhtml"
	"github.com/hypercodehq/libhtml/attr"
	"github.com/hypercodehq/hypercode/views/components/layouts"
	"github.com/hypercodehq/hypercode/views/components/ui"
)

type RepositoryWithOwner struct {
	Repository    *models.Repository
	OwnerUsername string
	StarCount     int64
}

type UserProfileData struct {
	User                  *models.User
	ProfileUser           *models.User
	Repositories          []*models.Repository
	RepositoriesWithOwner []RepositoryWithOwner
	StarCounts            map[int64]int64
	CurrentTab            string
	CanManage             bool
}

func UserProfile(r *http.Request, data *UserProfileData) html.Node {
	if data == nil || data.ProfileUser == nil {
		data = &UserProfileData{}
	}

	// Default to overview tab if not specified
	if data.CurrentTab == "" {
		data.CurrentTab = "overview"
	}

	var tabContent html.Node

	switch data.CurrentTab {
	case "repositories":
		tabContent = renderUserRepositoriesTab(data)
	case "stars":
		tabContent = renderUserStarsTab(data)
	default: // overview
		tabContent = renderUserOverviewTab(data)
	}

	return layouts.Profile(r,
		data.ProfileUser.DisplayName+" - Hypercode",
		layouts.ProfileLayoutOptions{
			Username:     data.ProfileUser.Username,
			DisplayName:  data.ProfileUser.DisplayName,
			IsOrg:        false,
			CurrentTab:   data.CurrentTab,
			ShowSettings: data.CanManage,
		},
		html.Main(
			attr.Class("w-full mx-auto max-w-7xl px-4 py-8"),
			html.Div(
				attr.Class("space-y-6"),
				// User header with display name
				html.Div(
					attr.Class("flex items-center gap-4 pb-6 border-b"),
					html.Div(
						attr.Class("flex items-center gap-3"),
						html.Div(
							attr.Class("p-3 rounded-full bg-muted"),
							ui.SVGIcon(ui.IconUser, "size-8"),
						),
						html.Div(
							attr.Class("flex flex-col"),
							html.H1(
								attr.Class("text-2xl font-semibold"),
								html.Text(data.ProfileUser.DisplayName),
							),
							html.P(
								attr.Class("text-muted-foreground"),
								html.Text("@"+data.ProfileUser.Username),
							),
						),
					),
				),
				// Tab content
				tabContent,
			),
		),
	)
}

func renderUserOverviewTab(data *UserProfileData) html.Node {
	if len(data.Repositories) == 0 {
		return ui.EmptyState(ui.EmptyStateProps{
			Icon:        ui.SVGIcon(ui.IconRepository, "size-6"),
			Title:       "No repositories yet",
			Description: "This user hasn't created any repositories yet.",
			ShowAction:  false,
		})
	}

	// Show top 6 repositories for overview
	repoCount := len(data.Repositories)
	if repoCount > 6 {
		repoCount = 6
	}

	repoCards := make([]html.Node, repoCount)
	for i := 0; i < repoCount; i++ {
		repo := data.Repositories[i]
		starCount := int64(0)
		if data.StarCounts != nil {
			starCount = data.StarCounts[repo.ID]
		}
		repoCards[i] = ui.RepositoryCard(ui.RepositoryCardProps{
			OwnerUsername: data.ProfileUser.Username,
			Name:          repo.Name,
			IsPublic:      repo.Visibility == "public",
			StarCount:     starCount,
		})
	}

	return html.Div(
		attr.Class("space-y-4"),
		html.Div(
			attr.Class("flex justify-between items-center"),
			html.H2(
				attr.Class("text-xl font-medium"),
				html.Text("Popular repositories"),
			),
			html.If(
				len(data.Repositories) > 6,
				html.A(
					attr.Href("/"+data.ProfileUser.Username+"/repositories"),
					attr.Class("text-sm text-primary hover:underline"),
					html.Text("View all repositories"),
				),
			),
		),
		html.Div(
			attr.Class("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"),
			html.For(repoCards, func(card html.Node) html.Node {
				return card
			}),
		),
	)
}

func renderUserRepositoriesTab(data *UserProfileData) html.Node {
	if len(data.Repositories) == 0 {
		return ui.EmptyState(ui.EmptyStateProps{
			Icon:        ui.SVGIcon(ui.IconRepository, "size-6"),
			Title:       "No repositories yet",
			Description: "This user hasn't created any repositories yet.",
			ShowAction:  false,
		})
	}

	repoCards := make([]html.Node, len(data.Repositories))
	for i, repo := range data.Repositories {
		starCount := int64(0)
		if data.StarCounts != nil {
			starCount = data.StarCounts[repo.ID]
		}
		repoCards[i] = ui.RepositoryCard(ui.RepositoryCardProps{
			OwnerUsername: data.ProfileUser.Username,
			Name:          repo.Name,
			IsPublic:      repo.Visibility == "public",
			StarCount:     starCount,
		})
	}

	return html.Div(
		attr.Class("space-y-4"),
		html.H2(
			attr.Class("text-xl font-medium"),
			html.Text("Repositories"),
		),
		html.Div(
			attr.Class("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"),
			html.For(repoCards, func(card html.Node) html.Node {
				return card
			}),
		),
	)
}

func renderUserStarsTab(data *UserProfileData) html.Node {
	if len(data.RepositoriesWithOwner) == 0 {
		return ui.EmptyState(ui.EmptyStateProps{
			Icon:        ui.SVGIcon(ui.IconStar, "size-6"),
			Title:       "No starred repositories yet",
			Description: "This user hasn't starred any repositories yet.",
			ShowAction:  false,
		})
	}

	repoCards := make([]html.Node, len(data.RepositoriesWithOwner))
	for i, repoWithOwner := range data.RepositoriesWithOwner {
		repoCards[i] = ui.RepositoryCard(ui.RepositoryCardProps{
			OwnerUsername: repoWithOwner.OwnerUsername,
			Name:          repoWithOwner.Repository.Name,
			IsPublic:      repoWithOwner.Repository.Visibility == "public",
			StarCount:     repoWithOwner.StarCount,
		})
	}

	return html.Div(
		attr.Class("space-y-4"),
		html.H2(
			attr.Class("text-xl font-medium"),
			html.Text("Starred repositories"),
		),
		html.Div(
			attr.Class("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"),
			html.For(repoCards, func(card html.Node) html.Node {
				return card
			}),
		),
	)
}