bumped bubbletea, lipgloss, bubbles, wish, and huh to v2. Mostly mechanical chur, new import paths, vewport getter/setter api, view() returns tea.view now, huh.theme became an interface so the theme package needed a small reshape. Render plumbing is gone (no more lipgloss.Render field on the Model or MakeRenderer in main.go). All pages still work over ssh. setting up for the refund composer next
237 lines
6.0 KiB
Go
237 lines
6.0 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"terminalShop/pkg/models"
|
|
|
|
"charm.land/bubbles/v2/viewport"
|
|
tea "charm.land/bubbletea/v2"
|
|
"charm.land/lipgloss/v2"
|
|
)
|
|
|
|
// RenderConfirmation renders the order confirmation view
|
|
func (m Model) RenderConfirmation() string {
|
|
titleStyle := m.theme.TextAccent().Bold(true).Padding(0, 0, 1, 0)
|
|
|
|
sectionStyle := m.theme.TextBody().Padding(0, 0, 1, 2)
|
|
|
|
labelStyle := m.theme.TextDim()
|
|
|
|
valueStyle := m.theme.TextAccent()
|
|
|
|
successStyle := m.theme.TextSuccess().Bold(true).Padding(1, 0)
|
|
|
|
title := titleStyle.Render("Order Confirmation")
|
|
|
|
// Shipping summary
|
|
shippingSection := ""
|
|
if m.confirm.shipping != nil {
|
|
shippingSection = sectionStyle.Render(
|
|
labelStyle.Render("Ship to: ") +
|
|
valueStyle.Render(m.confirm.shipping.Name) + "\n" +
|
|
labelStyle.Render(" ") +
|
|
valueStyle.Render(m.confirm.shipping.Street) + "\n" +
|
|
labelStyle.Render(" ") +
|
|
valueStyle.Render(fmt.Sprintf("%s, %s %s, %s", m.confirm.shipping.City, m.confirm.shipping.State, m.confirm.shipping.Zip, m.confirm.shipping.Country)),
|
|
)
|
|
}
|
|
|
|
// Items summary
|
|
cartSection := ""
|
|
for _, item := range m.confirm.items {
|
|
line := fmt.Sprintf(" %s x%d $%.2f", item.Coffee.Name, item.Quantity, float64(item.Coffee.Price*item.Quantity)/100)
|
|
cartSection += sectionStyle.Render(line) + "\n"
|
|
}
|
|
cartSection += sectionStyle.Render(
|
|
labelStyle.Render("Shipping: ")+valueStyle.Render("$0.00"),
|
|
) + "\n"
|
|
cartSection += sectionStyle.Render(
|
|
labelStyle.Render("Total: ") + valueStyle.Render(fmt.Sprintf("$%.2f", float64(m.confirm.total)/100)),
|
|
)
|
|
|
|
success := successStyle.Render("Order placed successfully!")
|
|
|
|
return lipgloss.JoinVertical(
|
|
lipgloss.Left,
|
|
title,
|
|
"",
|
|
shippingSection,
|
|
"",
|
|
cartSection,
|
|
"",
|
|
success,
|
|
)
|
|
}
|
|
|
|
func (m Model) updateConfirmViewport() Model {
|
|
headerH := lipgloss.Height(m.BuildHeader())
|
|
breadH := lipgloss.Height(m.BuildBreadcrumbs())
|
|
footerH := lipgloss.Height(m.BuildFooter())
|
|
availH := m.heightContainer - headerH - footerH - breadH
|
|
if availH < 1 {
|
|
availH = 1
|
|
}
|
|
if !m.confirm.viewportReady {
|
|
m.confirm.viewport = viewport.New(viewport.WithWidth(m.widthContent), viewport.WithHeight(availH))
|
|
m.confirm.viewport.KeyMap = viewport.DefaultKeyMap()
|
|
m.confirm.viewportReady = true
|
|
} else {
|
|
m.confirm.viewport.SetWidth(m.widthContent)
|
|
m.confirm.viewport.SetHeight(availH)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (m Model) ConfirmView() string {
|
|
if !m.confirm.viewportReady {
|
|
m = m.updateConfirmViewport()
|
|
}
|
|
m.confirm.viewport.SetContent(m.RenderConfirmation())
|
|
return lipgloss.Place(
|
|
m.widthContainer,
|
|
lipgloss.Height(m.confirm.viewport.View()),
|
|
lipgloss.Center, lipgloss.Center,
|
|
m.confirm.viewport.View(),
|
|
)
|
|
}
|
|
|
|
func (m Model) ConfirmUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
|
var cmd tea.Cmd
|
|
if m.confirm.viewportReady {
|
|
m.confirm.viewport, cmd = m.confirm.viewport.Update(msg)
|
|
}
|
|
|
|
keyMsg, ok := msg.(tea.KeyMsg)
|
|
if !ok {
|
|
return m, cmd
|
|
}
|
|
|
|
if keyMsg.String() != "esc" && keyMsg.String() != "s" {
|
|
return m, cmd
|
|
}
|
|
|
|
m.CheckingOut = false
|
|
m.confirm.total = 0
|
|
m.confirm.items = nil
|
|
m.confirm.shipping = nil
|
|
m, _ = m.ShopSwitch()
|
|
return m, func() tea.Msg {
|
|
if m.APIClient != nil {
|
|
_ = m.APIClient.ClearCart()
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func formatUSD(cents int) string {
|
|
return fmt.Sprintf("$%d.%02d", cents/100, cents%100)
|
|
}
|
|
|
|
func (m Model) generateReviewContent() string {
|
|
view := strings.Builder{}
|
|
|
|
if m.review.cardJustAdded {
|
|
if m.review.cardWasDuplicate {
|
|
view.WriteString(m.theme.TextDim().Render("identical card already saved... using existing card") + "\n\n")
|
|
} else {
|
|
view.WriteString(m.theme.TextSuccess().Bold(true).Render("card added successfully") + "\n\n")
|
|
}
|
|
}
|
|
|
|
if m.ShippingInfo != nil {
|
|
view.WriteString(m.ShippingInfo.Name + "\n")
|
|
view.WriteString(m.ShippingInfo.Street + "\n")
|
|
if m.ShippingInfo.Street2 != "" {
|
|
view.WriteString(m.ShippingInfo.Street2 + "\n")
|
|
}
|
|
fmt.Fprintf(&view, "%s, %s %s, %s\n", m.ShippingInfo.City, m.ShippingInfo.State, m.ShippingInfo.Zip, m.ShippingInfo.Country)
|
|
}
|
|
view.WriteString("\n")
|
|
|
|
if m.SelectedCard != nil {
|
|
fmt.Fprintf(&view, "cc: **** **** **** %s\n", m.SelectedCard.Last4)
|
|
}
|
|
view.WriteString("\n")
|
|
|
|
subtotal := m.CalculateSubtotal()
|
|
|
|
fmt.Fprintf(&view, "subtotal: %s\n", formatUSD(subtotal))
|
|
view.WriteString(m.theme.TextAccent().Render(fmt.Sprintf("total: %s", formatUSD(subtotal))) + "\n")
|
|
view.WriteString("\n")
|
|
view.WriteString(m.theme.TextBrand().Render("press enter to confirm") + "\n")
|
|
|
|
return m.theme.Base().Padding(0, 1).Render(view.String())
|
|
}
|
|
|
|
func (m Model) ReviewView() string {
|
|
if m.CheckingOut {
|
|
return m.theme.TextAccent().Bold(true).Padding(1).Render(" submitting order...")
|
|
}
|
|
if m.review.success {
|
|
content := m.theme.TextSuccess().Bold(true).Padding(1).Render("order placed successfully!")
|
|
return lipgloss.Place(
|
|
m.widthContainer,
|
|
lipgloss.Height(content),
|
|
lipgloss.Center, lipgloss.Center,
|
|
content,
|
|
)
|
|
}
|
|
content := m.generateReviewContent()
|
|
return lipgloss.Place(
|
|
m.widthContainer,
|
|
lipgloss.Height(content),
|
|
lipgloss.Center, lipgloss.Center,
|
|
content,
|
|
)
|
|
}
|
|
|
|
func (m Model) ReviewUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case CheckoutResultMsg:
|
|
m.CheckingOut = false
|
|
if msg.Err != nil {
|
|
m = m.SwitchPage(paymentPage)
|
|
m.error = &VisibleError{message: fmt.Sprintf("checkout failed: %v", msg.Err)}
|
|
m.review.cardJustAdded = false
|
|
m.payment.view = 0
|
|
m.payment.form = nil
|
|
return m, nil
|
|
}
|
|
m.Cart = make(map[uint]*models.CartItem)
|
|
m.cart.cursor = 0
|
|
m.ShippingInfo = nil
|
|
m.OrdersLoaded = false
|
|
m.review.success = true
|
|
return m, nil
|
|
|
|
case tea.KeyMsg:
|
|
if m.CheckingOut {
|
|
return m, nil
|
|
}
|
|
if m.review.success {
|
|
m.review.success = false
|
|
m.review.cardJustAdded = false
|
|
m.shipping.form = nil
|
|
m.payment.form = nil
|
|
m, _ = m.ShopSwitch()
|
|
return m, func() tea.Msg {
|
|
if m.APIClient != nil {
|
|
_ = m.APIClient.ClearCart()
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
switch msg.String() {
|
|
case "esc":
|
|
m = m.SwitchPage(paymentPage)
|
|
return m, nil
|
|
|
|
case "enter":
|
|
m.CheckingOut = true
|
|
return m, m.checkoutWithSavedCard()
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|