feat: throw out the boolean navigation with page routing

replaced ViewingCart/ViewingAccount booleans and CheckoutStep int with a
single 'page' type and a SwitchPage() method, this is just almost
copying the reference implementation (terminalDotShop)'s routing
pattern. Adds inCartFlow(), chechoutStep(), and buildPageContent()
helpers to clean up those huge if/else chains in views, footer, header,
and breadcrumds. code is looking a lot better now!
This commit is contained in:
2026-03-25 15:08:56 +01:00
parent 93cc69d028
commit 1a49360191
10 changed files with 129 additions and 185 deletions

BIN
api/main Executable file

Binary file not shown.

View File

@@ -729,22 +729,3 @@ func (c *Client) DeleteAddress(id uint) error {
return nil
}
func(c *Client) SetDefaultAddress(id uint) error {
url := fmt.Sprintf("%s/api/v1/addresses/%d/default", c.BaseURL, id)
req, err := http.NewRequest("PUT", url, nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.Token)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to set default address: status %d", resp.StatusCode)
}
return nil
}

View File

@@ -6,7 +6,7 @@ import (
func (m Model) BuildBreadcrumbs() string {
// Only show breadcrumbs in cart view
if !m.ViewingCart {
if !m.inCartFlow() {
return ""
}
@@ -31,7 +31,7 @@ func (m Model) BuildBreadcrumbs() string {
}
// Get current checkout step from model
currentStep := m.CheckoutStep
currentStep := m.checkoutStep()
var items []string
for i, label := range labels {

View File

@@ -36,7 +36,7 @@ func (m Model) BuildFooter() string {
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
} else if m.ViewingAccount {
} else if m.currentPage == accountPage {
switch m.OrderViewState {
case 2:
// Viewing single order detail
@@ -77,7 +77,7 @@ func (m Model) BuildFooter() string {
descStyle.Render("quit"),
)
}
} else if m.ViewingCart && m.CheckoutStep == 3 {
} else if m.currentPage == confirmPage {
// Confirmation screen
footerText = fmt.Sprintf("%s %s %s %s",
keybindStyle.Render("esc"),
@@ -85,7 +85,7 @@ func (m Model) BuildFooter() string {
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
} else if m.ViewingCart && m.CheckoutStep == 1 {
} else if m.currentPage == shippingPage {
if m.ShippingView == 0 && m.ShippingForm == nil {
// Address list
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s",
@@ -109,7 +109,7 @@ func (m Model) BuildFooter() string {
descStyle.Render("back"),
)
}
} else if m.ViewingCart && m.CheckoutStep == 2 {
} else if m.currentPage == paymentPage {
if m.PaymentView == 0 && m.PaymentForm == nil {
// Card list
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s",
@@ -136,7 +136,7 @@ func (m Model) BuildFooter() string {
descStyle.Render("back"),
)
}
} else if m.ViewingCart && m.CheckoutStep == 0 {
} else if m.currentPage == cartPage {
// In cart view, show proceed option
if len(m.Cart) > 0 {
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s",
@@ -173,16 +173,6 @@ func (m Model) BuildFooter() string {
descStyle.Render("quit"),
)
}
} else if m.ViewingCart {
// Other checkout steps
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"),
)
} else {
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),

View File

@@ -24,7 +24,7 @@ func (m Model) BuildHeader() string {
itemCountText := grayStyle.Render(fmt.Sprintf("[%d]", itemCount))
var cartKeybind, cartName, cartInfo string
if m.ViewingCart {
if m.inCartFlow() {
// Active: bold white keybind and name
cartKeybind = boldWhiteStyle.Render("c")
cartName = boldWhiteStyle.Render("cart")
@@ -57,7 +57,7 @@ func (m Model) BuildHeader() string {
separator := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).Render("|")
var shopKeybind, shopName string
if !m.ViewingCart && !m.ViewingAccount {
if m.currentPage == shopPage {
shopKeybind = boldWhiteStyle.Render("s")
shopName = boldWhiteStyle.Render("shop")
} else {
@@ -66,7 +66,7 @@ func (m Model) BuildHeader() string {
}
var accountKeybind, accountName string
if m.ViewingAccount {
if m.currentPage == accountPage {
accountKeybind = boldWhiteStyle.Render("a")
accountName = boldWhiteStyle.Render("account")
} else {

View File

@@ -21,10 +21,8 @@ func TestModel_BuildMenuView(t *testing.T) {
Cart map[uint]*models.CartItem
CartCursor int
AccountCursor int
CheckoutStep int
ScrollOffset int
ViewingCart bool
ViewingAccount bool
currentPage page
viewportWidth int
viewportHeight int
widthContainer int
@@ -68,10 +66,8 @@ func TestModel_BuildMenuView(t *testing.T) {
Cart: tt.fields.Cart,
CartCursor: tt.fields.CartCursor,
AccountCursor: tt.fields.AccountCursor,
CheckoutStep: tt.fields.CheckoutStep,
ScrollOffset: tt.fields.ScrollOffset,
ViewingCart: tt.fields.ViewingCart,
ViewingAccount: tt.fields.ViewingAccount,
currentPage: tt.fields.currentPage,
viewportWidth: tt.fields.viewportWidth,
viewportHeight: tt.fields.viewportHeight,
widthContainer: tt.fields.widthContainer,

View File

@@ -28,6 +28,18 @@ const (
large // viewportWidth >= 80
)
// page represetnts the currently active TUI screen
type page int
const (
shopPage page = iota
cartPage
shippingPage
paymentPage
confirmPage
accountPage
)
// This msg is sent after a debounce delay to apply a pending resize.
// the seq field makes sure that only the tick from the most recent resize is applied.
type resizeTickMsg struct {
@@ -54,18 +66,16 @@ type Model struct {
Cart map[uint]*models.CartItem // maps CoffeeID to cart item
CartCursor int // cursor position in cart view
AccountCursor int
CheckoutStep int // 0=cart, 1=shipping, 2=payment, 3=confirmation
currentPage page // currently active screen
switched bool // true when the page just changed (for state resets)
ScrollOffset int // scroll position for content
ViewingCart bool // true when viewing cart details
CheckingOut bool // true while saveCardAndConvert is running
lastCartUpdateID int64 // debounce ID for optimistic cart sync
ViewingAccount bool
// Menu modal state
ShowingMenu bool // true when full-screen menu is showing
ShowingHelp bool // true when help-screen is showing
menuLastCart bool // was viewing cart when menu was opened
menuLastAcct bool // was viewing account when menu was opened
menuLastPage page // page we were on before opening menu
// Layout fields — responsive container system
viewportWidth int // raw terminal width
@@ -109,6 +119,34 @@ type Model struct {
}
// SwitchPage changed the active page and marks that a switch happened
func (m Model) SwitchPage(p page) Model {
m.currentPage = p
m.switched = true
return m
}
// inCartFlow returns true when the user is anywhere in the cart or checkout flow
func (m Model) inCartFlow() bool {
return m.currentPage == cartPage ||
m.currentPage == shippingPage ||
m.currentPage == paymentPage ||
m.currentPage == confirmPage
}
func (m Model) checkoutStep() int {
switch m.currentPage {
case shippingPage:
return 1
case paymentPage:
return 2
case confirmPage:
return 3
default:
return 0
}
}
// updateLayout recalculates all layout variables from the raw viewport dimensions.
// This should be called whenever the terminal is resized.
func (m *Model) updateLayout(width, height int) {
@@ -210,9 +248,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch keyMsg.String() {
case "s":
m.ShowingMenu = false
m.ViewingCart = false
m.ViewingAccount = false
m.CheckoutStep = 0
m = m.SwitchPage(shopPage)
m.ShippingForm = nil
m.PaymentForm = nil
m.ScrollOffset = 0
@@ -220,9 +256,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.FaqFocused = false
case "a":
m.ShowingMenu = false
m.ViewingCart = false
m.ViewingAccount = true
m.CheckoutStep = 0
m = m.SwitchPage(accountPage)
m.ShippingForm = nil
m.PaymentForm = nil
m.ScrollOffset = 0
@@ -233,9 +267,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case "c":
m.ShowingMenu = false
m.ViewingCart = true
m.ViewingAccount = false
m.CheckoutStep = 0
m.SwitchPage(cartPage)
m.ShippingForm = nil
m.PaymentForm = nil
m.ScrollOffset = 0
@@ -266,7 +298,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
if m.ViewingCart && m.CheckoutStep == 1 {
if m.currentPage == shippingPage {
switch msg := msg.(type) {
case ShippingFormCompleteMsg:
addr := models.Address{
@@ -306,7 +338,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.ShippingInfo = &msg.Address
m.ShippingForm = nil
m.CheckoutStep = 2
m = m.SwitchPage(paymentPage)
m.SelectedCard = nil
return m, m.fetchCardsCmd()
@@ -329,7 +361,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.ErrorMsg = ""
switch keyMsg.String() {
case "esc":
m.CheckoutStep = 0
m = m.SwitchPage(cartPage)
m.ErrorMsg = ""
return m, nil
case "up", "k":
@@ -345,7 +377,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
addr := m.SavedAddresses[m.AddressCursor]
selected := addr // copy so we have a stable pointer
m.ShippingInfo = &selected
m.CheckoutStep = 2
m = m.SwitchPage(paymentPage)
m.SelectedCard = nil
return m, m.fetchCardsCmd()
}
@@ -382,8 +414,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
m.CheckoutStep = 0
m.ShippingForm = nil
m = m.SwitchPage(cartPage)
return m, nil
}
m.ErrorMsg = ""
@@ -395,7 +427,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
// When the payment form is active, route ALL messages to it.
if m.ViewingCart && m.CheckoutStep == 2 {
if m.currentPage == paymentPage {
switch msg := msg.(type) {
case CardsMsg:
@@ -406,7 +438,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, m.PaymentForm.form.Init()
}
m.SavedCards = msg.Cards
m.PaymentView = 0 // show card list
m.PaymentView = 0
m.CardCursor = 0
m.PaymentForm = nil
return m, nil
@@ -426,7 +458,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.CheckingOut = false
if msg.Err != nil {
m.ErrorMsg = fmt.Sprintf("checkout failed: %v", msg.Err)
m.CheckoutStep = 2
// already on paymentPage, no page switch needed
// Return to card list if we have saved cards, otherwise show form
if len(m.SavedCards) > 0 {
m.PaymentView = 0
@@ -438,7 +470,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, nil
}
m.CheckoutStep = 3
m = m.SwitchPage(confirmPage)
m.OrdersLoaded = false
return m, nil
case StripeTokenErrMsg:
@@ -479,8 +511,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.ErrorMsg = ""
switch keyMsg.String() {
case "esc":
m.CheckoutStep = 1
m.ErrorMsg = ""
m = m.SwitchPage(shippingPage)
return m, m.fetchAddressesCmd()
case "up", "k":
if m.CardCursor > 0 {
@@ -516,7 +548,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, m.deleteCardCmd(card.ID)
}
}
}
return m, nil
@@ -533,8 +564,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
// No saved cards - go back to shipping
m.CheckoutStep = 1
m.PaymentForm = nil
m = m.SwitchPage(shippingPage)
return m, m.fetchAddressesCmd()
}
m.ErrorMsg = ""
@@ -575,7 +606,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.ShippingInfo = &msg.Address
m.ShippingForm = nil
m.CheckoutStep = 2
m = m.SwitchPage(paymentPage)
m.SelectedCard = nil
return m, m.fetchCardsCmd()
@@ -583,20 +614,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.ErrorMsg = msg.Message
return m, nil
case PaymentFormCompleteMsg:
m.CheckoutStep = 3
m.PaymentForm = nil
return m, nil
case PaymentFormErrorMsg:
m.ErrorMsg = msg.Message
return m, nil
case StripeTokenMsg:
m.CheckoutStep = 3
m.PaymentForm = nil
return m, nil
case StripeTokenErrMsg:
m.ErrorMsg = fmt.Sprintf("payment failed: %v", msg.Err)
return m, nil
@@ -716,9 +733,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Quit
case "s":
m.ViewingCart = false
m.ViewingAccount = false
m.CheckoutStep = 0
m = m.SwitchPage(shopPage)
m.ShippingForm = nil
m.PaymentForm = nil
m.ScrollOffset = 0
@@ -726,9 +741,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.FaqFocused = false
case "c":
m.ViewingCart = true
m.ViewingAccount = false
m.CheckoutStep = 0
m = m.SwitchPage(cartPage)
m.ShippingForm = nil
m.PaymentForm = nil
m.ScrollOffset = 0
@@ -736,9 +749,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.FaqFocused = false
case "a":
m.ViewingCart = false
m.ViewingAccount = true
m.CheckoutStep = 0
m = m.SwitchPage(accountPage)
m.ShippingForm = nil
m.PaymentForm = nil
m.ScrollOffset = 0
@@ -751,13 +762,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "m":
m.ShowingMenu = true
m.menuLastCart = m.ViewingCart
m.menuLastAcct = m.ViewingAccount
m.menuLastPage = m.currentPage
return m, nil
case "p", "enter":
// Account view: enter order list or order detail
if m.ViewingAccount && m.AccountCursor == 0 && len(m.Orders) > 0 {
if m.currentPage == accountPage && m.AccountCursor == 0 && len(m.Orders) > 0 {
if m.OrderViewState == 0 {
m.OrderViewState = 1
m.OrderCursor = 0
@@ -770,34 +780,33 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
// Account view: focus into FAQ for scrolling
if m.ViewingAccount && m.AccountCursor == 1 && !m.FaqFocused {
if m.currentPage == accountPage && m.AccountCursor == 1 && !m.FaqFocused {
m.FaqFocused = true
m.ScrollOffset = 0
return m, nil
}
// Proceed to checkout from cart
if m.ViewingCart && m.CheckoutStep == 0 && len(m.Cart) > 0 {
m.CheckoutStep = 1
if m.currentPage == cartPage && len(m.Cart) > 0 {
m = m.SwitchPage(shippingPage)
return m, m.fetchAddressesCmd()
}
case "esc":
// Unfocus FAQ scrolling
if m.ViewingAccount && m.FaqFocused {
if m.currentPage == accountPage && m.FaqFocused {
m.FaqFocused = false
m.ScrollOffset = 0
return m, nil
}
// Navigate back through order view states
if m.ViewingAccount && m.OrderViewState > 0 {
if m.currentPage == accountPage && m.OrderViewState > 0 {
m.OrderViewState--
m.ScrollOffset = 0
return m, nil
}
// From confirmation, go back to shop
if m.ViewingCart && m.CheckoutStep == 3 {
m.ViewingCart = false
m.CheckoutStep = 0
if m.currentPage == confirmPage {
m = m.SwitchPage(shopPage)
m.ShippingForm = nil
m.PaymentForm = nil
m.ShippingInfo = nil
@@ -812,16 +821,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return nil
}
}
// Go back in checkout flow
if m.ViewingCart && m.CheckoutStep > 0 {
m.CheckoutStep--
m.ShippingForm = nil
m.PaymentForm = nil
return m, nil
}
case "up", "k":
if m.ViewingAccount {
if m.currentPage == accountPage {
if m.FaqFocused {
// Scroll FAQ content up
m.ScrollOffset -= 3
@@ -846,9 +848,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.AccountCursor--
m.ScrollOffset = 0
}
} else if !m.ViewingCart && !m.ViewingAccount && m.Cursor > 0 {
} else if m.currentPage == shopPage && m.Cursor > 0 {
m.Cursor--
} else if m.ViewingCart && m.CartCursor > 0 {
} else if m.currentPage == cartPage && m.CartCursor > 0 {
m.CartCursor--
// Auto-scroll to keep cursor visible (each cart item is ~5 lines)
itemHeight := 5
@@ -859,7 +861,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case "down", "j":
if m.ViewingAccount {
if m.currentPage == accountPage {
if m.FaqFocused {
// Scroll FAQ content down
m.ScrollOffset += 3
@@ -894,9 +896,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.AccountCursor++
m.ScrollOffset = 0
}
} else if !m.ViewingCart && !m.ViewingAccount && m.Cursor < len(m.Coffees)-1 {
} else if m.currentPage == shopPage && m.Cursor < len(m.Coffees)-1 {
m.Cursor++
} else if m.ViewingCart && m.CartCursor < len(m.Cart)-1 {
} else if m.currentPage == cartPage && m.CartCursor < len(m.Cart)-1 {
m.CartCursor++
// Auto-scroll to keep cursor visible (each cart item is ~5 lines)
itemHeight := 5
@@ -919,7 +921,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case "pgup", "ctrl+u":
if m.ViewingAccount {
if m.currentPage == accountPage {
// Only scroll when focused into scrollable content
if m.FaqFocused || m.OrderViewState >= 2 {
m.ScrollOffset -= 3
@@ -936,7 +938,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case "pgdown", "ctrl+d":
if m.ViewingAccount {
if m.currentPage == accountPage {
// Only scroll when focused into scrollable content
if m.FaqFocused || m.OrderViewState >= 2 {
m.ScrollOffset += 3
@@ -951,7 +953,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case "+", "=":
if !m.ViewingCart && !m.ViewingAccount {
if m.currentPage == shopPage {
// Increment quantity in shop view (keyed by CoffeeID)
coffeeID := m.Coffees[m.Cursor].ID
if item, exists := m.Cart[coffeeID]; exists {
@@ -964,7 +966,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
return m, m.syncCartItemCmd(coffeeID, m.Cart[coffeeID].Quantity)
} else if m.ViewingCart {
} else if m.currentPage == cartPage {
// Increment quantity in cart view
cartItems := m.GetCartItemsSlice()
if m.CartCursor >= 0 && m.CartCursor < len(cartItems) {
@@ -974,7 +976,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case "-", "_":
if !m.ViewingCart && !m.ViewingAccount {
if m.currentPage == shopPage {
// Decrement quantity in shop view (keyed by CoffeeID)
coffeeID := m.Coffees[m.Cursor].ID
if item, exists := m.Cart[coffeeID]; exists {
@@ -992,7 +994,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, m.syncCartItemCmd(coffeeID, newQty)
}
} else if m.ViewingCart {
} else if m.currentPage == cartPage {
// Decrement quantity in cart view
cartItems := m.GetCartItemsSlice()
if m.CartCursor >= 0 && m.CartCursor < len(cartItems) {
@@ -1487,8 +1489,6 @@ func NewModel(username string) Model {
AccountCursor: 0,
OrderCursor: 0,
OrderViewState: 0,
ViewingCart: false,
ViewingAccount: false,
Loading: true,
APIClient: api.NewClient("http://localhost:8080", ""),
StripeKey: os.Getenv("STRIPE_PUBLIC_KEY"),

View File

@@ -21,10 +21,8 @@ func TestModel_updateLayout(t *testing.T) {
Cart map[uint]*models.CartItem
CartCursor int
AccountCursor int
CheckoutStep int
ScrollOffset int
ViewingCart bool
ViewingAccount bool
currentPage page
viewportWidth int
viewportHeight int
widthContainer int
@@ -72,10 +70,8 @@ func TestModel_updateLayout(t *testing.T) {
Cart: tt.fields.Cart,
CartCursor: tt.fields.CartCursor,
AccountCursor: tt.fields.AccountCursor,
CheckoutStep: tt.fields.CheckoutStep,
ScrollOffset: tt.fields.ScrollOffset,
ViewingCart: tt.fields.ViewingCart,
ViewingAccount: tt.fields.ViewingAccount,
currentPage: tt.fields.currentPage,
viewportWidth: tt.fields.viewportWidth,
viewportHeight: tt.fields.viewportHeight,
widthContainer: tt.fields.widthContainer,

View File

@@ -68,62 +68,16 @@ func (m Model) View() string {
marginBottom = 0
}
// Build main content based on view
var content string
if m.Loading {
loadingStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("205")).
Padding(2, 4)
loadingStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Padding(2, 4)
content = loadingStyle.Render("Loading products from API...")
} else if m.ErrorMsg != "" {
errorStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("196")).
Background(lipgloss.Color("52")).
Padding(0, 1).
MarginBottom(1)
errorBanner := errorStyle.Render("⚠ " + m.ErrorMsg)
if m.ViewingAccount {
content = errorBanner + "\n" + m.BuildAccountView(availableContentHeight)
} else if m.ViewingCart && m.CheckoutStep == 1 {
if m.ShippingView == 0 && m.ShippingForm == nil {
content = errorBanner + "\n" + m.RenderAddressList()
} else if m.ShippingForm != nil {
content = errorBanner + "\n" + m.RenderShippingForm(m.ShippingForm)
}
} else if m.ViewingCart && m.CheckoutStep == 2 && m.CheckingOut {
content = errorBanner + "\n submitting order..."
} else if m.ViewingCart && m.CheckoutStep == 2 && m.PaymentView == 0 && m.PaymentForm == nil {
content = errorBanner + "\n" + m.RenderCardList()
} else if m.ViewingCart && m.CheckoutStep == 2 && m.PaymentForm != nil {
content = errorBanner + "\n" + m.RenderPaymentForm(m.PaymentForm)
} else if m.ViewingCart && m.CheckoutStep == 3 {
content = errorBanner + "\n" + m.RenderConfirmation()
} else if m.ViewingCart {
content = errorBanner + "\n" + m.BuildCartView()
} else {
content = errorBanner + "\n" + m.BuildShopView()
}
} else if m.ViewingAccount {
content = m.BuildAccountView(availableContentHeight)
} else if m.ViewingCart && m.CheckoutStep == 1 {
if m.ShippingView == 0 && m.ShippingForm == nil {
content = m.RenderAddressList()
} else if m.ShippingForm != nil {
content = m.RenderShippingForm(m.ShippingForm)
}
} else if m.ViewingCart && m.CheckoutStep == 2 && m.CheckingOut {
content = " submitting order..."
} else if m.ViewingCart && m.CheckoutStep == 2 && m.PaymentView == 0 && m.PaymentForm == nil {
content = m.RenderCardList()
} else if m.ViewingCart && m.CheckoutStep == 2 && m.PaymentForm != nil {
content = m.RenderPaymentForm(m.PaymentForm)
} else if m.ViewingCart && m.CheckoutStep == 3 {
content = m.RenderConfirmation()
} else if m.ViewingCart {
content = m.BuildCartView()
errorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Background(lipgloss.Color("52")).Padding(0, 1).MarginBottom(1)
errorBanner := errorStyle.Render(m.ErrorMsg)
content = errorBanner + "\n" + m.buildPageContent(availableContentHeight)
} else {
content = m.BuildShopView()
content = m.buildPageContent(availableContentHeight)
}
// Split content into lines and handle scrolling
@@ -132,7 +86,7 @@ func (m Model) View() string {
// Skip global scroll when account view handles its own scrolling
// (order list/detail and FAQ focused use internal scrolling in BuildAccountView)
accountHandlesScroll := m.ViewingAccount && (m.OrderViewState >= 1 || m.FaqFocused)
accountHandlesScroll := m.currentPage == accountPage && (m.OrderViewState >= 1 || m.FaqFocused)
if !accountHandlesScroll {
// Ensure scroll offset is within bounds
if m.ScrollOffset < 0 {
@@ -202,3 +156,29 @@ func (m Model) View() string {
constrained,
)
}
func (m Model) buildPageContent(height int) string {
switch m.currentPage {
case accountPage:
return m.BuildAccountView(height)
case shippingPage:
if m.ShippingView == 0 && m.ShippingForm == nil {
return m.RenderAddressList()
}
return m.RenderShippingForm(m.ShippingForm)
case paymentPage:
if m.CheckingOut {
return " submitting order..."
}
if m.PaymentView == 0 && m.PaymentForm == nil {
return m.RenderCardList()
}
return m.RenderPaymentForm(m.PaymentForm)
case confirmPage:
return m.RenderConfirmation()
case cartPage:
return m.BuildCartView()
default:
return m.BuildShopView()
}
}

1
terminal-shop-source Submodule

Submodule terminal-shop-source added at 697730e73c