diff --git a/api/main b/api/main new file mode 100755 index 0000000..58aab4e Binary files /dev/null and b/api/main differ diff --git a/pkg/api/client.go b/pkg/api/client.go index 2e85c0a..22e7570 100644 --- a/pkg/api/client.go +++ b/pkg/api/client.go @@ -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 -} diff --git a/pkg/tui/breadcrumbs.go b/pkg/tui/breadcrumbs.go index f2b1ba2..d0d3d09 100644 --- a/pkg/tui/breadcrumbs.go +++ b/pkg/tui/breadcrumbs.go @@ -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 { diff --git a/pkg/tui/footer.go b/pkg/tui/footer.go index 6ed377c..0a2f85c 100644 --- a/pkg/tui/footer.go +++ b/pkg/tui/footer.go @@ -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"), diff --git a/pkg/tui/header.go b/pkg/tui/header.go index 76cee24..9062227 100644 --- a/pkg/tui/header.go +++ b/pkg/tui/header.go @@ -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 { diff --git a/pkg/tui/menu_test.go b/pkg/tui/menu_test.go index 0389365..e2d9b00 100644 --- a/pkg/tui/menu_test.go +++ b/pkg/tui/menu_test.go @@ -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, diff --git a/pkg/tui/model.go b/pkg/tui/model.go index 5b4ca38..ce328b1 100644 --- a/pkg/tui/model.go +++ b/pkg/tui/model.go @@ -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"), diff --git a/pkg/tui/model_test.go b/pkg/tui/model_test.go index ce3b51b..dced1ea 100644 --- a/pkg/tui/model_test.go +++ b/pkg/tui/model_test.go @@ -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, diff --git a/pkg/tui/views.go b/pkg/tui/views.go index 6543186..2ae8858 100644 --- a/pkg/tui/views.go +++ b/pkg/tui/views.go @@ -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() + } +} diff --git a/terminal-shop-source b/terminal-shop-source new file mode 160000 index 0000000..697730e --- /dev/null +++ b/terminal-shop-source @@ -0,0 +1 @@ +Subproject commit 697730e73cba0ef0bab943bbd75e5bcd64f5f5d3