1 Commits

Author SHA1 Message Date
d66b41eed3 refactor: refactor the footer handling 2026-04-24 12:13:26 +02:00
5 changed files with 177 additions and 175 deletions

View File

@@ -356,55 +356,110 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
m.account.orderViewState = 1 m.account.orderViewState = 1
m.account.orderCursor = 0 m.account.orderCursor = 0
m.account.scrollOffset = 0 m.account.scrollOffset = 0
m.footer = []footerCommand{
{key: "j/k", value: "orders"},
{key: "enter", value: "details"},
{key: "esc", value: "back"},
{key: "q", value: "quit"},
}
} else if m.account.orderViewState == 1 { } else if m.account.orderViewState == 1 {
m.account.orderViewState = 2 m.account.orderViewState = 2
m.account.scrollOffset = 0 m.account.scrollOffset = 0
m.footer = []footerCommand{
{key: "esc", value: "back"},
{key: "s", value: "shop"},
{key: "q", value: "quit"},
}
} }
} }
case "ssh keys": case "ssh keys":
if !m.account.sshKeyListFocused { if !m.account.sshKeyListFocused {
m.account.sshKeyListFocused = true m.account.sshKeyListFocused = true
m.account.sshKeyCursor = 0 m.account.sshKeyCursor = 0
m.footer = []footerCommand{
{key: "j/k", value: "keys"},
{key: "x", value: "delete"},
{key: "esc", value: "back"},
}
} }
case "addresses": case "addresses":
if !m.account.addressListFocused { if !m.account.addressListFocused {
m.account.addressListFocused = true m.account.addressListFocused = true
m.account.addressCursor = 0 m.account.addressCursor = 0
m.footer = []footerCommand{
{key: "j/k", value: "addresses"},
{key: "x", value: "delete"},
{key: "esc", value: "back"},
}
} }
case "cards": case "cards":
if !m.account.cardListFocused { if !m.account.cardListFocused {
m.account.cardListFocused = true m.account.cardListFocused = true
m.account.cardCursor = 0 m.account.cardCursor = 0
m.footer = []footerCommand{
{key: "j/k", value: "cards"},
{key: "x", value: "delete"},
{key: "esc", value: "back"},
}
} }
case "faq": case "faq":
if !m.account.faqFocused { if !m.account.faqFocused {
m.account.faqFocused = true m.account.faqFocused = true
m.account.scrollOffset = 0 m.account.scrollOffset = 0
m.footer = []footerCommand{
{key: "j/k", value: "scroll"},
{key: "esc", value: "back"},
{key: "s", value: "shop"},
{key: "c", value: "cart"},
{key: "q", value: "quit"},
}
} }
} }
case "esc": case "esc":
accountDefaultFooter := []footerCommand{
{key: "j/k", value: "navigate"},
{key: "enter", value: "select"},
{key: "s", value: "shop"},
{key: "c", value: "cart"},
{key: "?", value: "help"},
{key: "q", value: "quit"},
}
if m.account.addressDeleting != nil { if m.account.addressDeleting != nil {
m.account.addressDeleting = nil m.account.addressDeleting = nil
} else if m.account.addressListFocused { } else if m.account.addressListFocused {
m.account.addressListFocused = false m.account.addressListFocused = false
m.account.addressCursor = 0 m.account.addressCursor = 0
m.footer = accountDefaultFooter
} else if m.account.cardDeleting != nil { } else if m.account.cardDeleting != nil {
m.account.cardDeleting = nil m.account.cardDeleting = nil
} else if m.account.cardListFocused { } else if m.account.cardListFocused {
m.account.cardListFocused = false m.account.cardListFocused = false
m.account.cardCursor = 0 m.account.cardCursor = 0
m.footer = accountDefaultFooter
} else if m.account.sshKeyDeleting != nil { } else if m.account.sshKeyDeleting != nil {
m.account.sshKeyDeleting = nil m.account.sshKeyDeleting = nil
} else if m.account.sshKeyListFocused { } else if m.account.sshKeyListFocused {
m.account.sshKeyListFocused = false m.account.sshKeyListFocused = false
m.account.sshKeyCursor = 0 m.account.sshKeyCursor = 0
m.footer = accountDefaultFooter
} else if m.account.faqFocused { } else if m.account.faqFocused {
m.account.faqFocused = false m.account.faqFocused = false
m.account.scrollOffset = 0 m.account.scrollOffset = 0
m.footer = accountDefaultFooter
} else if m.account.orderViewState > 0 { } else if m.account.orderViewState > 0 {
m.account.orderViewState-- m.account.orderViewState--
m.account.scrollOffset = 0 m.account.scrollOffset = 0
if m.account.orderViewState == 1 {
m.footer = []footerCommand{
{key: "j/k", value: "orders"},
{key: "enter", value: "details"},
{key: "esc", value: "back"},
{key: "q", value: "quit"},
}
} else {
m.footer = accountDefaultFooter
}
} }
} }
return m, nil return m, nil

View File

@@ -1,194 +1,25 @@
package tui package tui
import ( import (
"fmt" "strings"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
) )
func (m Model) BuildFooter() string { func (m Model) BuildFooter() string {
// Style for keybinds (bold)
keybindStyle := m.theme.TextDim().Bold(true) keybindStyle := m.theme.TextDim().Bold(true)
// Style for descriptions (normal)
descStyle := m.theme.TextDim() descStyle := m.theme.TextDim()
// On small terminals, only show the menu hint
if m.size == small { if m.size == small {
footerText := keybindStyle.Render("m") + " " + descStyle.Render("menu") footerText := keybindStyle.Render("m") + " " + descStyle.Render("menu")
return lipgloss.NewStyle().Align(lipgloss.Center).Width(m.widthContainer).Render(footerText) return lipgloss.NewStyle().Align(lipgloss.Center).Width(m.widthContainer).Render(footerText)
} }
var footerText string parts := []string{}
if m.account.faqFocused { for _, cmd := range m.footer {
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s", parts = append(parts, keybindStyle.Render(cmd.key)+" "+descStyle.Render(cmd.value))
keybindStyle.Render("j/k"),
descStyle.Render("scroll"),
keybindStyle.Render("esc"),
descStyle.Render("back"),
keybindStyle.Render("s"),
descStyle.Render("shop"),
keybindStyle.Render("c"),
descStyle.Render("cart"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
} else if m.currentPage == accountPage {
switch m.account.orderViewState {
case 2:
// Viewing single order detail
footerText = fmt.Sprintf("%s %s %s %s %s %s",
keybindStyle.Render("esc"),
descStyle.Render("back"),
keybindStyle.Render("s"),
descStyle.Render("shop"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
case 1:
// Browsing order list
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),
descStyle.Render("orders"),
keybindStyle.Render("enter"),
descStyle.Render("details"),
keybindStyle.Render("esc"),
descStyle.Render("back"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
default:
// Account tabs
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),
descStyle.Render("navigate"),
keybindStyle.Render("enter"),
descStyle.Render("select"),
keybindStyle.Render("s"),
descStyle.Render("shop"),
keybindStyle.Render("c"),
descStyle.Render("cart"),
keybindStyle.Render("?"),
descStyle.Render("help"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
}
} else if m.currentPage == confirmPage {
// Confirmation screen
footerText = fmt.Sprintf("%s %s %s %s",
keybindStyle.Render("esc"),
descStyle.Render("back to shop"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
} else if m.currentPage == shippingPage {
if m.shipping.view == 0 && m.shipping.form == nil {
// Address list
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),
descStyle.Render("addresses"),
keybindStyle.Render("enter"),
descStyle.Render("select"),
keybindStyle.Render("d/x"),
descStyle.Render("delete"),
keybindStyle.Render("esc"),
descStyle.Render("back"),
)
} else {
// Shipping form
footerText = fmt.Sprintf("%s %s %s %s %s %s",
keybindStyle.Render("tab"),
descStyle.Render("next"),
keybindStyle.Render("enter"),
descStyle.Render("submit"),
keybindStyle.Render("esc"),
descStyle.Render("back"),
)
}
} else if m.currentPage == paymentPage {
if m.payment.view == 0 && m.payment.form == nil {
// Card list
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),
descStyle.Render("cards"),
keybindStyle.Render("enter"),
descStyle.Render("select"),
keybindStyle.Render("d/x"),
descStyle.Render("delete"),
keybindStyle.Render("esc"),
descStyle.Render("back"),
)
} else if m.CheckingOut {
// Submitting order
footerText = descStyle.Render("submitting order...")
} else {
// Payment form
footerText = fmt.Sprintf("%s %s %s %s %s %s",
keybindStyle.Render("tab"),
descStyle.Render("next"),
keybindStyle.Render("enter"),
descStyle.Render("submit"),
keybindStyle.Render("esc"),
descStyle.Render("back"),
)
}
} else if m.currentPage == cartPage {
// In cart view, show proceed option
if !m.IsCartEmpty() {
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),
descStyle.Render("items"),
keybindStyle.Render("+/-"),
descStyle.Render("qty"),
keybindStyle.Render("p/enter"),
descStyle.Render("checkout"),
keybindStyle.Render("s"),
descStyle.Render("shop"),
keybindStyle.Render("a"),
descStyle.Render("account"),
keybindStyle.Render("pgup/pgdn"),
descStyle.Render("scroll"),
keybindStyle.Render("?"),
descStyle.Render("help"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
} else {
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),
descStyle.Render("items"),
keybindStyle.Render("+/-"),
descStyle.Render("qty"),
keybindStyle.Render("s"),
descStyle.Render("shop"),
keybindStyle.Render("a"),
descStyle.Render("account"),
keybindStyle.Render("?"),
descStyle.Render("help"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
}
} else {
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),
descStyle.Render("products"),
keybindStyle.Render("+/-"),
descStyle.Render("qty"),
keybindStyle.Render("c"),
descStyle.Render("cart"),
keybindStyle.Render("a"),
descStyle.Render("account"),
keybindStyle.Render("?"),
descStyle.Render("help"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
} }
footerText := strings.Join(parts, " ")
return lipgloss.NewStyle(). return lipgloss.NewStyle().Align(lipgloss.Center).Width(m.widthContainer).Render(footerText)
Align(lipgloss.Center).
Width(m.widthContainer).
Render(footerText)
} }

View File

@@ -53,6 +53,11 @@ type resizeTickMsg struct {
// this prevents flickering when resizing the terminal // this prevents flickering when resizing the terminal
const resizeDebounce = 50 * time.Millisecond const resizeDebounce = 50 * time.Millisecond
type footerCommand struct {
key string
value string
}
type Model struct { type Model struct {
// User authentication // User authentication
User *models.User User *models.User
@@ -121,6 +126,9 @@ type Model struct {
account accountState account accountState
review reviewState review reviewState
confirm confirmState confirm confirmState
// Footer
footer []footerCommand
} }
type shopState struct { type shopState struct {
@@ -208,18 +216,55 @@ func (m Model) SwitchPage(p page) Model {
func (m Model) ShopSwitch() (Model, tea.Cmd) { func (m Model) ShopSwitch() (Model, tea.Cmd) {
m = m.SwitchPage(shopPage) m = m.SwitchPage(shopPage)
m = m.updateShopViewports() m = m.updateShopViewports()
m.footer = []footerCommand{
{key: "j/k", value: "products"},
{key: "+/-", value: "qty"},
{key: "c", value: "cart"},
{key: "a", value: "account"},
{key: "?", value: "help"},
{key: "q", value: "quit"},
}
return m, nil return m, nil
} }
func (m Model) CartSwitch() (Model, tea.Cmd) { func (m Model) CartSwitch() (Model, tea.Cmd) {
m = m.SwitchPage(cartPage) m = m.SwitchPage(cartPage)
m = m.updateCartViewport() m = m.updateCartViewport()
if m.IsCartEmpty() {
m.footer = []footerCommand{
{key: "j/k", value: "items"},
{key: "+/-", value: "qty"},
{key: "s", value: "shop"},
{key: "a", value: "account"},
{key: "?", value: "help"},
{key: "q", value: "quit"},
}
} else {
m.footer = []footerCommand{
{key: "j/k", value: "items"},
{key: "+/-", value: "qty"},
{key: "p/enter", value: "checkout"},
{key: "s", value: "shop"},
{key: "a", value: "account"},
{key: "pgup/pgdn", value: "scroll"},
{key: "?", value: "help"},
{key: "q", value: "quit"},
}
}
return m, nil return m, nil
} }
func (m Model) AccountSwitch() (Model, tea.Cmd) { func (m Model) AccountSwitch() (Model, tea.Cmd) {
m = m.SwitchPage(accountPage) m = m.SwitchPage(accountPage)
m.account = accountState{} m.account = accountState{}
m.footer = []footerCommand{
{key: "j/k", value: "navigate"},
{key: "enter", value: "select"},
{key: "s", value: "shop"},
{key: "?", value: "help"},
{key: "q", value: "quit"},
}
if !m.OrdersLoaded { if !m.OrdersLoaded {
return m, m.fetchOrdersCmd() return m, m.fetchOrdersCmd()
} }
@@ -230,6 +275,12 @@ func (m Model) ShippingSwitch() (Model, tea.Cmd) {
m = m.SwitchPage(shippingPage) m = m.SwitchPage(shippingPage)
m.shipping = shippingState{} m.shipping = shippingState{}
m = m.updateShippingViewport() m = m.updateShippingViewport()
m.footer = []footerCommand{
{key: "j/k", value: "addresses"},
{key: "enter", value: "select"},
{key: "d/x", value: "delete"},
{key: "esc", value: "back"},
}
return m, m.fetchAddressesCmd() return m, m.fetchAddressesCmd()
} }
@@ -237,11 +288,21 @@ func (m Model) PaymentSwitch() (Model, tea.Cmd) {
m = m.SwitchPage(paymentPage) m = m.SwitchPage(paymentPage)
m.payment = paymentState{} m.payment = paymentState{}
m.SelectedCard = nil m.SelectedCard = nil
m.footer = []footerCommand{
{key: "j/k", value: "cards"},
{key: "enter", value: "select"},
{key: "d/x", value: "delete"},
{key: "esc", value: "back"},
}
return m, m.fetchCardsCmd() return m, m.fetchCardsCmd()
} }
func (m Model) ReviewSwitch() (Model, tea.Cmd) { func (m Model) ReviewSwitch() (Model, tea.Cmd) {
m = m.SwitchPage(reviewPage) m = m.SwitchPage(reviewPage)
m.footer = []footerCommand{
{key: "enter", value: "confirm"},
{key: "esc", value: "back"},
}
return m, nil return m, nil
} }
@@ -249,6 +310,10 @@ func (m Model) ConfirmSwitch() (Model, tea.Cmd) {
m = m.SwitchPage(confirmPage) m = m.SwitchPage(confirmPage)
m.confirm = confirmState{} m.confirm = confirmState{}
m = m.updateConfirmViewport() m = m.updateConfirmViewport()
m.footer = []footerCommand{
{key: "esc", value: "back to shop"},
{key: "q", value: "quit"},
}
return m, nil return m, nil
} }

View File

@@ -434,6 +434,11 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
m.ErrorMsg = fmt.Sprintf("failed to save card: %v", msg.Err) m.ErrorMsg = fmt.Sprintf("failed to save card: %v", msg.Err)
m.payment.view = 1 m.payment.view = 1
m.payment.form = m.InitPaymentForm() m.payment.form = m.InitPaymentForm()
m.footer = []footerCommand{
{key: "tab", value: "next"},
{key: "enter", value: "submit"},
{key: "esc", value: "back"},
}
return m, m.payment.form.form.Init() return m, m.payment.form.form.Init()
} }
m.SavedCards = append(m.SavedCards, msg.Card) m.SavedCards = append(m.SavedCards, msg.Card)
@@ -495,12 +500,20 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
if m.payment.cardCursor == len(m.SavedCards) { if m.payment.cardCursor == len(m.SavedCards) {
m.payment.view = 1 m.payment.view = 1
m.payment.form = m.InitPaymentForm() m.payment.form = m.InitPaymentForm()
m.footer = []footerCommand{
{key: "tab", value: "next"},
{key: "enter", value: "submit"},
{key: "esc", value: "back"},
}
return m, m.payment.form.form.Init() return m, m.payment.form.form.Init()
} }
// Browser payment // Browser payment
m.payment.view = 2 m.payment.view = 2
m.payment.collectURL = nil m.payment.collectURL = nil
m.payment.collectCardCount = len(m.SavedCards) m.payment.collectCardCount = len(m.SavedCards)
m.footer = []footerCommand{
{key: "esc", value: "back"},
}
return m, m.collectCardCmd() return m, m.collectCardCmd()
case "d", "x": case "d", "x":
@@ -513,6 +526,11 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
if len(m.SavedCards) == 0 { if len(m.SavedCards) == 0 {
m.payment.view = 1 m.payment.view = 1
m.payment.form = m.InitPaymentForm() m.payment.form = m.InitPaymentForm()
m.footer = []footerCommand{
{key: "tab", value: "next"},
{key: "enter", value: "submit"},
{key: "esc", value: "back"},
}
return m, tea.Batch(m.payment.form.form.Init(), m.deleteCardCmd(card.ID)) return m, tea.Batch(m.payment.form.form.Init(), m.deleteCardCmd(card.ID))
} }
return m, m.deleteCardCmd(card.ID) return m, m.deleteCardCmd(card.ID)
@@ -526,6 +544,12 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
if keyMsg, ok := msg.(tea.KeyMsg); ok && keyMsg.String() == "esc" { if keyMsg, ok := msg.(tea.KeyMsg); ok && keyMsg.String() == "esc" {
m.payment.view = 0 m.payment.view = 0
m.payment.collectURL = nil m.payment.collectURL = nil
m.footer = []footerCommand{
{key: "j/k", value: "cards"},
{key: "enter", value: "select"},
{key: "d/x", value: "delete"},
{key: "esc", value: "back"},
}
return m, nil return m, nil
} }
return m, nil return m, nil
@@ -539,6 +563,12 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
if len(m.SavedCards) > 0 { if len(m.SavedCards) > 0 {
m.payment.view = 0 m.payment.view = 0
m.payment.form = nil m.payment.form = nil
m.footer = []footerCommand{
{key: "j/k", value: "cards"},
{key: "enter", value: "select"},
{key: "d/x", value: "delete"},
{key: "esc", value: "back"},
}
return m, nil return m, nil
} }
m.payment.form = nil m.payment.form = nil

View File

@@ -362,6 +362,11 @@ func (m Model) ShippingUpdate(msg tea.Msg) (Model, tea.Cmd) {
m.SavedAddresses = nil m.SavedAddresses = nil
m.shipping.view = 1 m.shipping.view = 1
m.shipping.form = m.InitShippingForm() m.shipping.form = m.InitShippingForm()
m.footer = []footerCommand{
{key: "tab", value: "next"},
{key: "enter", value: "submit"},
{key: "esc", value: "back"},
}
return m, m.shipping.form.form.Init() return m, m.shipping.form.form.Init()
} }
m.SavedAddresses = msg.Addresses m.SavedAddresses = msg.Addresses
@@ -412,6 +417,11 @@ func (m Model) ShippingUpdate(msg tea.Msg) (Model, tea.Cmd) {
} }
m.shipping.view = 1 m.shipping.view = 1
m.shipping.form = m.InitShippingForm() m.shipping.form = m.InitShippingForm()
m.footer = []footerCommand{
{key: "tab", value: "next"},
{key: "enter", value: "submit"},
{key: "esc", value: "back"},
}
return m, m.shipping.form.form.Init() return m, m.shipping.form.form.Init()
case "d", "x": case "d", "x":
if m.shipping.addressCursor < len(m.SavedAddresses) { if m.shipping.addressCursor < len(m.SavedAddresses) {
@@ -423,6 +433,11 @@ func (m Model) ShippingUpdate(msg tea.Msg) (Model, tea.Cmd) {
if len(m.SavedAddresses) == 0 { if len(m.SavedAddresses) == 0 {
m.shipping.view = 1 m.shipping.view = 1
m.shipping.form = m.InitShippingForm() m.shipping.form = m.InitShippingForm()
m.footer = []footerCommand{
{key: "tab", value: "next"},
{key: "enter", value: "submit"},
{key: "esc", value: "back"},
}
return m, tea.Batch(m.shipping.form.form.Init(), m.deleteAddressCmd(addr.ID)) return m, tea.Batch(m.shipping.form.form.Init(), m.deleteAddressCmd(addr.ID))
} }
return m, m.deleteAddressCmd(addr.ID) return m, m.deleteAddressCmd(addr.ID)
@@ -439,6 +454,12 @@ func (m Model) ShippingUpdate(msg tea.Msg) (Model, tea.Cmd) {
if len(m.SavedAddresses) > 0 { if len(m.SavedAddresses) > 0 {
m.shipping.view = 0 m.shipping.view = 0
m.shipping.form = nil m.shipping.form = nil
m.footer = []footerCommand{
{key: "j/k", value: "addresses"},
{key: "enter", value: "select"},
{key: "d/x", value: "delete"},
{key: "esc", value: "back"},
}
return m, nil return m, nil
} }
m.shipping.form = nil m.shipping.form = nil