mirror of
https://github.com/marcusDenslow/terminalShop.git
synced 2026-10-26 18:28:47 +01:00
feat: implement server-side cart persistence with Stripe PaymentIntents Replaced the client-only in-mem cart with a database-backed cart system - Add Cart and CartItem GORM models (two-table design) - Add 6 cart API endpoints (get, set item, set address, set card, clear, conver) - Switch from Stripe Charge API to PaymentIntents with confirm:true - Add 9 API client methods for cart and card operations - Refactor TUI cart from map[int] (slice index) to map[uint] (CoffeeID) - Replace single-shot checkout with saveCardAndConvert multi-step flow - Add CheckingOut state with "submitting order..." processing indicator - Fix Stripe tokenization to use secret key for server-side SSH apps - Remove admin product CRUD endpoints and old checkout handler
115 lines
3.0 KiB
Go
115 lines
3.0 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
func (m Model) BuildShopView() string {
|
|
leftWidth := 18
|
|
rightWidth := m.widthContent - leftWidth - 2 // 2 for gap
|
|
|
|
// On small/medium, items are centered and span the full container width.
|
|
// On large, items are left-aligned in the narrow left column.
|
|
itemWidth := leftWidth - 2
|
|
itemAlign := lipgloss.Left
|
|
if m.size < large {
|
|
itemWidth = m.widthContainer
|
|
itemAlign = lipgloss.Center
|
|
}
|
|
|
|
// Build the left panel (coffee list)
|
|
leftPanel := ""
|
|
for i, coffee := range m.Coffees {
|
|
if m.Cursor == i {
|
|
style := lipgloss.NewStyle().
|
|
Background(lipgloss.Color(coffee.Color)).
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Padding(0, 1).
|
|
Width(itemWidth).
|
|
Align(itemAlign)
|
|
leftPanel += style.Render(coffee.Name) + "\n"
|
|
} else {
|
|
style := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#AAAAAA")).
|
|
Padding(0, 1).
|
|
Width(itemWidth).
|
|
Align(itemAlign)
|
|
leftPanel += style.Render(coffee.Name) + "\n"
|
|
}
|
|
}
|
|
|
|
var leftContainer lipgloss.Style
|
|
if m.size < large {
|
|
leftContainer = lipgloss.NewStyle().Width(m.widthContainer)
|
|
} else {
|
|
leftContainer = lipgloss.NewStyle().Width(leftWidth)
|
|
}
|
|
|
|
// Build the detail view (right panel)
|
|
detailView := ""
|
|
if m.Cursor >= 0 && m.Cursor < len(m.Coffees) {
|
|
selectedCoffee := m.Coffees[m.Cursor]
|
|
|
|
nameStyle := lipgloss.NewStyle().
|
|
Bold(true).
|
|
Foreground(lipgloss.Color("#FFFFFF"))
|
|
|
|
infoStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#AAAAAA"))
|
|
|
|
priceStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color(selectedCoffee.Color)).
|
|
Bold(true)
|
|
|
|
// On small terminals, use full content width for description
|
|
descWidth := rightWidth - 2
|
|
if m.size < large {
|
|
descWidth = m.widthContent - 2
|
|
}
|
|
|
|
descStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Width(descWidth)
|
|
|
|
quantityStyle := lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#FFFFFF")).
|
|
Bold(true)
|
|
|
|
// Get current quantity (look up by CoffeeID)
|
|
quantity := 0
|
|
if item, exists := m.Cart[selectedCoffee.ID]; exists {
|
|
quantity = item.Quantity
|
|
}
|
|
|
|
detailView += nameStyle.Render(selectedCoffee.Name) + "\n"
|
|
detailView += infoStyle.Render(fmt.Sprintf("%s | %doz | %s",
|
|
selectedCoffee.RoastType,
|
|
selectedCoffee.Ounces,
|
|
selectedCoffee.BeanType)) + "\n\n"
|
|
detailView += priceStyle.Render(fmt.Sprintf("$%.2f", selectedCoffee.Price)) + "\n\n"
|
|
detailView += descStyle.Render(selectedCoffee.Description) + "\n\n"
|
|
detailView += quantityStyle.Render(fmt.Sprintf("- %d +", quantity)) + "\n"
|
|
}
|
|
|
|
detailContainer := lipgloss.NewStyle().
|
|
Width(rightWidth)
|
|
|
|
// Responsive layout: side-by-side on large, stacked on small/medium
|
|
if m.size < large {
|
|
return lipgloss.JoinVertical(
|
|
lipgloss.Left,
|
|
leftContainer.Render(leftPanel),
|
|
detailContainer.Width(m.widthContainer).Render(detailView),
|
|
)
|
|
}
|
|
|
|
return lipgloss.JoinHorizontal(
|
|
lipgloss.Top,
|
|
leftContainer.Render(leftPanel),
|
|
" ",
|
|
detailContainer.Render(detailView),
|
|
)
|
|
}
|