diff --git a/pkg/tui/account.go b/pkg/tui/account.go index 3a5a2fe..63e8e53 100644 --- a/pkg/tui/account.go +++ b/pkg/tui/account.go @@ -1,7 +1,6 @@ package tui import ( - "fmt" "strings" tea "github.com/charmbracelet/bubbletea" @@ -45,124 +44,15 @@ func (m Model) BuildAccountView(availableHeight int) string { selectedItem := models.AccountMenuItems[m.account.cursor] switch selectedItem { case "order history": - if !m.OrdersLoaded { - detailView = titleStyle.Render("Order History") + "\n\n" + contentStyle.Render("Loading orders...") - } else if len(m.Orders) == 0 { - detailView = titleStyle.Render("Order History") + "\n\n" + contentStyle.Render("No orders yet.") - } else if m.account.orderViewState == 2 { - // State 2: full detail for the selected order - detailView = m.buildOrderDetailView(m.Orders[m.account.orderCursor], detailContentWidth) - } else { - // State 0 (preview) or State 1 (browsing with cursor) - boxWidth := detailContentWidth - 4 - boxPadding := 0 - if m.heightContainer >= 25 { - boxPadding = 1 - } - - lines := titleStyle.Render("Order History") + "\n\n" - for i, order := range m.Orders { - isSelected := m.account.orderViewState == 1 && i == m.account.orderCursor - lines += m.buildOrderCard(order, boxWidth, isSelected, boxPadding, detailContentWidth) - if i < len(m.Orders)-1 { - lines += "\n" - } - } - - if m.account.orderViewState == 0 { - hintStyle := m.theme.TextDim() - lines += "\n" + hintStyle.Render("enter: browse orders") - } - detailView = lines - } - + detailView = m.OrdersView(detailContentWidth) case "addresses": - lines := titleStyle.Render("Address") + "\n\n" - if len(m.SavedAddresses) == 0 { - lines += contentStyle.Render("No saved addresses") - } else { - for i, addr := range m.SavedAddresses { - isSelected := m.account.addressListFocused && i == m.account.addressCursor - label := addr.Name + " " + addr.Street + ", " + addr.City - if len(label) > detailContentWidth-4 { - label = label[:detailContentWidth-4] - } - if m.account.addressDeleting != nil && *m.account.addressDeleting == i { - lines += m.theme.TextError().Bold(true).Render(" deletes? (y/n)") + "\n" - } else if isSelected { - lines += m.theme.TextAccent().Bold(true).Render("> "+label) + "\n" - } else { - lines += contentStyle.Render(" "+label) + "\n" - } - } - } - if m.account.addressListFocused { - lines += "\n" + m.theme.TextDim().Render("x: delete esc: back") - } else { - lines += "\n" + m.theme.TextDim().Render("enter: manage") - } - detailView = lines - + detailView = m.AddressesView(detailContentWidth) case "cards": - lines := titleStyle.Render("Cards") + "\n\n" - if len(m.SavedCards) == 0 { - lines += contentStyle.Render("No saved cards.") - } else { - for i, card := range m.SavedCards { - isSelected := m.account.cardListFocused && i == m.account.cardCursor - label := fmt.Sprintf("**** **** **** %s %s exp %02d/%02d", - card.Last4, card.Brand, card.ExpMonth, card.ExpYear%100) - if m.account.cardDeleting != nil && *m.account.cardDeleting == i { - lines += m.theme.TextError().Bold(true).Render(" delete? (y/n)") + "\n" - } else if isSelected { - lines += m.theme.TextAccent().Bold(true).Render("> "+label) + "\n" - } else { - lines += contentStyle.Render(" "+label) + "\n" - } - } - } - if m.account.cardListFocused { - lines += "\n" + m.theme.TextDim().Render("x: delete esc: back") - } else { - lines += "\n" + m.theme.TextDim().Render("enter: manage") - } - detailView = lines - + detailView = m.CardsView(detailContentWidth) case "ssh keys": - lines := titleStyle.Render("SSH keys") + "\n\n" - if !m.SSHKeysLoaded { - lines += contentStyle.Render("Loading ssh keys...") - } else if len(m.SSHKeys) == 0 { - lines += contentStyle.Render("No saved ssh keys.") - } else { - for i, key := range m.SSHKeys { - isSelected := m.account.sshKeyListFocused && i == m.account.sshKeyCursor - label := key.Fingerprint - if key.Comment != "" { - label += " " + key.Comment - } - if len(label) > detailContentWidth-4 { - label = label[:detailContentWidth-4] - } - if m.account.sshKeyDeleting != nil && *m.account.sshKeyDeleting == i { - lines += m.theme.TextError().Bold(true).Render(" delete? (y/n)") + "\n" - } else if isSelected { - lines += m.theme.TextAccent().Bold(true).Render("> "+label) + "\n" - } else { - lines += contentStyle.Render(" "+label) + "\n" - } - } - } - if m.account.sshKeyListFocused { - lines += "\n" + m.theme.TextDim().Render("x: delete esc: back") - } else { - lines += "\n" + m.theme.TextDim().Render("enter: manage") - } - detailView = lines - + detailView = m.SSHKeysView(detailContentWidth) case "faq": questionStyle := m.theme.TextHighlight().Bold(true) - faqContent := "" for i, faq := range m.FAQs { faqContent += questionStyle.Render(wordWrap(faq.Question, detailContentWidth)) + "\n" @@ -177,26 +67,7 @@ func (m Model) BuildAccountView(availableHeight int) string { detailView += "\n\n" + hintStyle.Render("enter: scroll faq") } case "about": - accentStyle := m.theme.TextHighlight().Bold(true) - userStyle := m.theme.TextAccent().Bold(true) - - aboutContent := contentStyle.Render(wordWrap( - "This project is heavily inspired by the wonderful team who made TerminalDotShop. I want to thank them for open-sourcing the code for that project. Cudos to those smart, funny, amazing looking devs:", - detailContentWidth, - )) + "\n\n" - aboutContent += userStyle.Render("@thdxr") + "\n" - aboutContent += userStyle.Render("@adamdotdev") + "\n" - aboutContent += userStyle.Render("@theprimeagen") + "\n" - aboutContent += userStyle.Render("@teej_dv") + "\n" - aboutContent += userStyle.Render("@iamdavidhill") + "\n\n" - aboutContent += contentStyle.Render("And me:") + "\n\n" - aboutContent += userStyle.Render("@marcusDenslow") + "\n\n" - aboutContent += contentStyle.Render(wordWrap("This project tries to copy the idea of terminalDotShop, but with one twist: write everything in GO. this is my humble attempt", - detailContentWidth)) + "\n" - aboutContent += accentStyle.Render("Terminal Products, Inc.") - - detailView = titleStyle.Render("About") + "\n\n" + aboutContent - + detailView = m.AboutView(detailContentWidth) } } @@ -260,152 +131,31 @@ func (m Model) BuildAccountView(availableHeight int) string { ) } -// buildOrderCard renders a single order as a bordered box for the order list. -// Shows order number, date, status, total, and the first 2 items as a preview. -func (m Model) buildOrderCard(order models.Order, boxWidth int, isSelected bool, boxPadding int, containerWidth int) string { - // Order header line: "Order #123" left, "Jan 02 2026 PAID" right - nameStyle := m.theme.TextAccent().Bold(true) +func (m Model) AboutView(width int) string { + titleStyle := m.theme.TextAccent().Bold(true).MarginBottom(2) + contentStyle := m.theme.TextBody().Width(width) + accentStyle := m.theme.TextHighlight().Bold(true) + userStyle := m.theme.TextAccent().Bold(true) - dimStyle := m.theme.TextDim() + aboutContent := contentStyle.Render(wordWrap( + "This project is heavily inspired by the wonderful team who made TerminalDotShop. I want to thank them for open-sourcing the code for that project. Cudos to those smart, funny, amazing looking devs:", + width, + )) + "\n\n" + aboutContent += userStyle.Render("@thdxr") + "\n" + aboutContent += userStyle.Render("@adamdotdev") + "\n" + aboutContent += userStyle.Render("@theprimeagen") + "\n" + aboutContent += userStyle.Render("@teej_dv") + "\n" + aboutContent += userStyle.Render("@iamdavidhill") + "\n\n" + aboutContent += contentStyle.Render("And me:") + "\n\n" + aboutContent += userStyle.Render("@marcusDenslow") + "\n\n" + aboutContent += contentStyle.Render(wordWrap( + "This project tries to copy the idea of terminalDotShop, but with one twist: write everything in GO. this is my humble attempt", + width, + )) + "\n" + aboutContent += accentStyle.Render("Terminal Products, Inc.") - statusStyle := m.theme.TextHighlight().Bold(true) + return titleStyle.Render("About") + "\n\n" + aboutContent - orderLabel := nameStyle.Render(fmt.Sprintf("Order #%d", order.ID)) - total := nameStyle.Render(fmt.Sprintf("$%.2f", float64(order.Total)/100.0)) - date := dimStyle.Render(order.CreatedAt.Format("Jan 02 2006")) - status := statusStyle.Render(strings.ToUpper(string(order.Status))) - - rightSide := date + " " + status + " " + total - rightWidth := lipgloss.Width(rightSide) - leftWidth := lipgloss.Width(orderLabel) - spacing := boxWidth - leftWidth - rightWidth - 4 // 4 for padding - if spacing < 1 { - spacing = 1 - } - - line1 := orderLabel + lipgloss.NewStyle().Width(spacing).Render("") + rightSide - - // Item preview lines (show first 2 items) - var lines []string - lines = append(lines, line1) - - previewCount := 2 - if len(order.Items) < previewCount { - previewCount = len(order.Items) - } - for i := 0; i < previewCount; i++ { - item := order.Items[i] - itemLine := dimStyle.Render(fmt.Sprintf("%dx %s - $%.2f", item.Quantity, item.Name, float64(item.Price)/100.0)) - lines = append(lines, itemLine) - } - if len(order.Items) > 2 { - extra := len(order.Items) - 2 - moreText := "item" - if extra > 1 { - moreText = "items" - } - lines = append(lines, dimStyle.Render(fmt.Sprintf("+ %d more %s", extra, moreText))) - } - - boxContent := strings.Join(lines, "\n") - - // Create bordered box — highlight if selected - var itemBox lipgloss.Style - if isSelected { - itemBox = lipgloss.NewStyle(). - Border(lipgloss.NormalBorder(), true). - BorderForeground(m.theme.Accent()). - Padding(boxPadding, 2). - Width(boxWidth) - } else { - itemBox = lipgloss.NewStyle(). - Border(lipgloss.NormalBorder()). - BorderForeground(m.theme.Border()). - Padding(boxPadding, 2). - Width(boxWidth) - } - - centered := lipgloss.NewStyle(). - Width(containerWidth). - Align(lipgloss.Center) - - return centered.Render(itemBox.Render(boxContent)) -} - -// buildOrderDetailView renders the full detail view for a single order. -func (m Model) buildOrderDetailView(order models.Order, width int) string { - titleStyle := m.theme.TextAccent().Bold(true).MarginBottom(1) - - labelStyle := m.theme.TextHighlight().Bold(true) - - valueStyle := m.theme.TextBody() - - dimStyle := m.theme.TextDim() - - var b strings.Builder - - // Header - b.WriteString(titleStyle.Render(fmt.Sprintf("Order #%d", order.ID))) - b.WriteString("\n\n") - - // Status and date - b.WriteString(labelStyle.Render("Status: ")) - b.WriteString(valueStyle.Render(strings.ToUpper(string(order.Status)))) - b.WriteString("\n") - b.WriteString(labelStyle.Render("Date: ")) - b.WriteString(valueStyle.Render(order.CreatedAt.Format("Jan 02 2006 3:04 PM"))) - b.WriteString("\n\n") - - // Items - b.WriteString(labelStyle.Render("Items")) - b.WriteString("\n") - b.WriteString(dimStyle.Render(strings.Repeat("-", 40))) - b.WriteString("\n") - for _, item := range order.Items { - price := fmt.Sprintf("$%.2f", float64(item.Price)/100.0) - line := fmt.Sprintf(" %dx %-20s %s", item.Quantity, item.Name, price) - b.WriteString(valueStyle.Render(line)) - b.WriteString("\n") - } - b.WriteString(dimStyle.Render(strings.Repeat("-", 40))) - b.WriteString("\n") - - // Totals - subtotal := fmt.Sprintf("$%.2f", float64(order.Subtotal)/100.0) - shipping := fmt.Sprintf("$%.2f", float64(order.ShippingCost)/100.0) - total := fmt.Sprintf("$%.2f", float64(order.Total)/100.0) - b.WriteString(valueStyle.Render(fmt.Sprintf(" %-22s %s", "Subtotal", subtotal))) - b.WriteString("\n") - b.WriteString(valueStyle.Render(fmt.Sprintf(" %-22s %s", "Shipping", shipping))) - b.WriteString("\n") - b.WriteString(labelStyle.Render(fmt.Sprintf(" %-22s %s", "Total", total))) - b.WriteString("\n\n") - - // Shipping address - b.WriteString(labelStyle.Render("Ship To")) - b.WriteString("\n") - b.WriteString(valueStyle.Render(" " + order.ShippingName)) - b.WriteString("\n") - b.WriteString(valueStyle.Render(" " + order.ShippingStreet)) - b.WriteString("\n") - if order.ShippingStreet2 != "" { - b.WriteString(valueStyle.Render(" " + order.ShippingStreet2)) - b.WriteString("\n") - } - cityLine := order.ShippingCity - if order.ShippingState != "" { - cityLine += ", " + order.ShippingState - } - cityLine += " " + order.ShippingZip - b.WriteString(valueStyle.Render(" " + cityLine)) - b.WriteString("\n") - b.WriteString(valueStyle.Render(" " + order.ShippingCountry)) - b.WriteString("\n\n") - - // Footer hint - b.WriteString(dimStyle.Render("esc: back to orders")) - - return b.String() } // computeFaqScrollMax calculates the maximum scroll offset for the FAQ content. diff --git a/pkg/tui/account_addresses.go b/pkg/tui/account_addresses.go new file mode 100644 index 0000000..d37b017 --- /dev/null +++ b/pkg/tui/account_addresses.go @@ -0,0 +1,35 @@ +package tui + + +func (m Model) AddressesView(width int) string { + titleStyle := m.theme.TextAccent().Bold(true).MarginBottom(2) + activeStyle := m.theme.TextAccent().Bold(true) + contentStyle := m.theme.TextBody().Width(width) + + lines := titleStyle.Render("Address") + "\n\n" + if len(m.SavedAddresses) == 0 { + lines += contentStyle.Render("No saved addresses") + } else { + for i, addr := range m.SavedAddresses { + isSelected := m.account.addressListFocused && i == m.account.addressCursor + label := addr.Name + " " + addr.Street + ", " + addr.City + if len(label) > width-4 { + label = label[:width-4] + } + if m.account.addressDeleting != nil && *m.account.addressDeleting == i { + lines += m.theme.TextError().Bold(true).Render(" deletes? (y/n)") + "\n" + } else if isSelected { + lines += activeStyle.Render("> "+label) + "\n" + } else { + lines += contentStyle.Render(" "+label) + "\n" + } + } + } + if m.account.addressListFocused { + lines += "\n" + m.theme.TextDim().Render("x: delete esc: back") + } else { + lines += "\n" + m.theme.TextDim().Render("enter: manage") + } + return lines +} + diff --git a/pkg/tui/account_cards.go b/pkg/tui/account_cards.go new file mode 100644 index 0000000..098e3ec --- /dev/null +++ b/pkg/tui/account_cards.go @@ -0,0 +1,36 @@ +package tui + +import ( + "fmt" +) + +func (m Model) CardsView(width int) string { + titleSTyle := m.theme.TextAccent().Bold(true).MarginBottom(2) + contentStyle := m.theme.TextBody().Width(width) + activeStyle := m.theme.TextAccent().Bold(true) + + + lines := titleSTyle.Render("Cards") + "\n\n" + if m.SavedCardsIsEmpty() { + lines += contentStyle.Render("No saved cards") + } else { + for i, card := range m.SavedCards { + isSelected := m.account.cardListFocused && i == m.account.cardCursor + label := fmt.Sprintf("**** **** **** %s %s exp %02d/%02d", card.Last4, card.Brand, card.ExpMonth, card.ExpYear%100) + if m.account.cardDeleting != nil && *m.account.cardDeleting == i { + lines += m.theme.TextError().Bold(true).Render(" delete? (y/s)") + "\n" + } else if isSelected { + lines += activeStyle.Render("> "+label) + "\n" + } else { + lines += contentStyle.Render(" "+label) + "\n" + } + } + } + if m.account.cardListFocused { + lines += "\n" + m.theme.TextDim().Render("x: delete esc: back") + } else { + lines += "\n" + m.theme.TextDim().Render("enter: manage") + } + + return lines +} diff --git a/pkg/tui/account_orders.go b/pkg/tui/account_orders.go new file mode 100644 index 0000000..23415f1 --- /dev/null +++ b/pkg/tui/account_orders.go @@ -0,0 +1,194 @@ +package tui + +import ( + "fmt" + "strings" + + "terminalShop/pkg/models" + + "github.com/charmbracelet/lipgloss" +) + +func (m Model) OrdersView(width int) string { + titleStyle := m.theme.TextAccent().Bold(true).MarginBottom(2) + contentStyle := m.theme.TextBody().Width(width) + + if !m.OrdersLoaded { + return titleStyle.Render("Order History") + "\n\n" + contentStyle.Render("Loading orders...") + } + if len(m.Orders) == 0 { + return titleStyle.Render("Order History") + "\n\n" + contentStyle.Render("No orders yet") + } + if m.account.orderViewState == 2 { + return m.buildOrderDetailView(m.Orders[m.account.orderCursor], width) + } + + boxWidth := width - 4 + boxPadding := 0 + if m.heightContainer >= 25 { + boxPadding = 1 + } + + lines := titleStyle.Render("Order History") + "\n\n" + for i, order := range m.Orders { + isSelected := m.account.orderViewState == 1 && i == m.account.orderCursor + lines += m.buildOrderCard(order, boxWidth, isSelected, boxPadding, width) + if i < len(m.Orders)-1 { + lines += "\n" + } + } + + if m.account.orderViewState == 0 { + hintStyle := m.theme.TextDim() + lines += "\n" + hintStyle.Render("enter: browse orders") + } + return lines +} + +// buildOrderCard renders a single order as a bordered box for the order list. +// Shows order number, date, status, total, and the first 2 items as a preview. +func (m Model) buildOrderCard(order models.Order, boxWidth int, isSelected bool, boxPadding int, containerWidth int) string { + // Order header line: "Order #123" left, "Jan 02 2026 PAID" right + nameStyle := m.theme.TextAccent().Bold(true) + + dimStyle := m.theme.TextDim() + + statusStyle := m.theme.TextHighlight().Bold(true) + + orderLabel := nameStyle.Render(fmt.Sprintf("Order #%d", order.ID)) + total := nameStyle.Render(fmt.Sprintf("$%.2f", float64(order.Total)/100.0)) + date := dimStyle.Render(order.CreatedAt.Format("Jan 02 2006")) + status := statusStyle.Render(strings.ToUpper(string(order.Status))) + + rightSide := date + " " + status + " " + total + rightWidth := lipgloss.Width(rightSide) + leftWidth := lipgloss.Width(orderLabel) + spacing := boxWidth - leftWidth - rightWidth - 4 // 4 for padding + if spacing < 1 { + spacing = 1 + } + + line1 := orderLabel + lipgloss.NewStyle().Width(spacing).Render("") + rightSide + + // Item preview lines (show first 2 items) + var lines []string + lines = append(lines, line1) + + previewCount := 2 + if len(order.Items) < previewCount { + previewCount = len(order.Items) + } + for i := 0; i < previewCount; i++ { + item := order.Items[i] + itemLine := dimStyle.Render(fmt.Sprintf("%dx %s - $%.2f", item.Quantity, item.Name, float64(item.Price)/100.0)) + lines = append(lines, itemLine) + } + if len(order.Items) > 2 { + extra := len(order.Items) - 2 + moreText := "item" + if extra > 1 { + moreText = "items" + } + lines = append(lines, dimStyle.Render(fmt.Sprintf("+ %d more %s", extra, moreText))) + } + + boxContent := strings.Join(lines, "\n") + + // Create bordered box — highlight if selected + var itemBox lipgloss.Style + if isSelected { + itemBox = lipgloss.NewStyle(). + Border(lipgloss.NormalBorder(), true). + BorderForeground(m.theme.Accent()). + Padding(boxPadding, 2). + Width(boxWidth) + } else { + itemBox = lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(m.theme.Border()). + Padding(boxPadding, 2). + Width(boxWidth) + } + + centered := lipgloss.NewStyle(). + Width(containerWidth). + Align(lipgloss.Center) + + return centered.Render(itemBox.Render(boxContent)) +} + +// buildOrderDetailView renders the full detail view for a single order +func (m Model) buildOrderDetailView(order models.Order, width int) string { + titleStyle := m.theme.TextAccent().Bold(true).MarginBottom(1) + + labelStyle := m.theme.TextHighlight().Bold(true) + + valueStyle := m.theme.TextBody() + + dimStyle := m.theme.TextDim() + + var b strings.Builder + + // Header + b.WriteString(titleStyle.Render(fmt.Sprintf("Order #%d", order.ID))) + b.WriteString("\n\n") + + // Status and date + b.WriteString(labelStyle.Render("Status: ")) + b.WriteString(valueStyle.Render(strings.ToUpper(string(order.Status)))) + b.WriteString("\n") + b.WriteString(labelStyle.Render("Date: ")) + b.WriteString(valueStyle.Render(order.CreatedAt.Format("Jan 02 2006 3:04 PM"))) + b.WriteString("\n\n") + + // Items + b.WriteString(labelStyle.Render("Items")) + b.WriteString("\n") + b.WriteString(dimStyle.Render(strings.Repeat("-", 40))) + b.WriteString("\n") + for _, item := range order.Items { + price := fmt.Sprintf("$%.2f", float64(item.Price)/100.0) + line := fmt.Sprintf(" %dx %-20s %s", item.Quantity, item.Name, price) + b.WriteString(valueStyle.Render(line)) + b.WriteString("\n") + } + b.WriteString(dimStyle.Render(strings.Repeat("-", 40))) + b.WriteString("\n") + + // Totals + subtotal := fmt.Sprintf("$%.2f", float64(order.Subtotal)/100.0) + shipping := fmt.Sprintf("$%.2f", float64(order.ShippingCost)/100.0) + total := fmt.Sprintf("$%.2f", float64(order.Total)/100.0) + b.WriteString(valueStyle.Render(fmt.Sprintf(" %-22s %s", "Subtotal", subtotal))) + b.WriteString("\n") + b.WriteString(valueStyle.Render(fmt.Sprintf(" %-22s %s", "Shipping", shipping))) + b.WriteString("\n") + b.WriteString(labelStyle.Render(fmt.Sprintf(" %-22s %s", "Total", total))) + b.WriteString("\n\n") + + // Shipping address + b.WriteString(labelStyle.Render("Ship To")) + b.WriteString("\n") + b.WriteString(valueStyle.Render(" " + order.ShippingName)) + b.WriteString("\n") + b.WriteString(valueStyle.Render(" " + order.ShippingStreet)) + b.WriteString("\n") + if order.ShippingStreet2 != "" { + b.WriteString(valueStyle.Render(" " + order.ShippingStreet2)) + b.WriteString("\n") + } + cityLine := order.ShippingCity + if order.ShippingState != "" { + cityLine += ", " + order.ShippingState + } + cityLine += " " + order.ShippingZip + b.WriteString(valueStyle.Render(" " + cityLine)) + b.WriteString("\n") + b.WriteString(valueStyle.Render(" " + order.ShippingCountry)) + b.WriteString("\n\n") + + // Footer hint + b.WriteString(dimStyle.Render("esc: back to orders")) + + return b.String() +} diff --git a/pkg/tui/account_sshkeys.go b/pkg/tui/account_sshkeys.go new file mode 100644 index 0000000..dbb6ba6 --- /dev/null +++ b/pkg/tui/account_sshkeys.go @@ -0,0 +1,41 @@ + +package tui + +func (m Model) SSHKeysView(width int) string { + titleSTyle := m.theme.TextAccent().Bold(true).MarginBottom(2) + contentStyle := m.theme.TextBody().Width(width) + activeStyle := m.theme.TextAccent().Bold(true) + + + lines := titleSTyle.Render("SSH keys") + "\n\n" + if !m.SSHKeysLoaded { + lines += contentStyle.Render("Loading ssh keys...") + } else if len(m.SSHKeys) == 0 { + lines += contentStyle.Render("No saved ssh keys") + } else { + for i, key := range m.SSHKeys { + isSelected := m.account.sshKeyListFocused && i == m.account.sshKeyCursor + label := key.Fingerprint + if key.Comment != "" { + label += " " + key.Comment + } + if len(label) > width-4 { + label += label[:width-4] + } + if m.account.sshKeyDeleting != nil && *m.account.sshKeyDeleting == i { + lines += m.theme.TextError().Bold(true).Render(" delete? (y/n)") + "\n" + } else if isSelected { + lines += activeStyle.Render("> "+label) + "\n" + } else { + lines += contentStyle.Render(" "+label) + "\n" + } + } + } + if m.account.sshKeyListFocused { + lines += "\n" + m.theme.TextDim().Render("x: delete esc: back") + } else { + lines += "\n" + m.theme.TextDim().Render("enter: manage") + } + + return lines +} diff --git a/pkg/tui/model.go b/pkg/tui/model.go index e052331..2a4cff3 100644 --- a/pkg/tui/model.go +++ b/pkg/tui/model.go @@ -778,6 +778,14 @@ func (m Model) CalculateSubtotal() int { return subtotal } +func (m Model) SavedAddressesIsEmpty() bool { + return len(m.SavedAddresses) == 0 +} + +func (m Model) SavedCardsIsEmpty() bool { + return len(m.SavedCards) == 0 +} + // NewModel creates a new model using the default renderer. func NewModel(username string) Model { return newModelWithRenderer(username, lipgloss.DefaultRenderer())