fix: store coffee prices as int cents to prevent rounding errors

prices stored as float64 could lead to rounding errors when converting to cents for stripe (19.99 * 100 = 1998.99... truncating to 1998). canged Coffee.Price to int, storing cents directly. Updated the seed data, fallback products, API handlers, and all TUI display code to be in sync with this change.
This commit is contained in:
2026-03-26 09:09:20 +01:00
parent f13bfd6572
commit 93b257a612
11 changed files with 33 additions and 746 deletions

View File

@@ -50,14 +50,13 @@ func cartResponse(cart *models.Cart) map[string]interface{} {
subtotal := 0
itemsData := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
priceInCents := int(item.Coffee.Price * 100)
lineTotal := priceInCents * item.Quantity
lineTotal := item.Coffee.Price * item.Quantity
subtotal += lineTotal
itemsData = append(itemsData, map[string]interface{}{
"id": item.ID,
"coffee_id": item.CoffeeID,
"quantity": item.Quantity,
"subtotal": lineTotal,
"subtotal": lineTotal, // already in cents
"coffee": item.Coffee,
})
}
@@ -296,13 +295,12 @@ func (h *CartHandler) ConvertCart(w http.ResponseWriter, r *http.Request) {
var subtotal int
var orderItems []models.OrderItem
for _, item := range items {
priceInCents := int(item.Coffee.Price * 100)
lineTotal := priceInCents * item.Quantity
lineTotal := item.Coffee.Price * item.Quantity
subtotal += lineTotal
orderItems = append(orderItems, models.OrderItem{
CoffeeID: item.CoffeeID,
Name: item.Coffee.Name,
Price: priceInCents,
Price: item.Coffee.Price,
Quantity: item.Quantity,
})
}

View File

@@ -1,710 +0,0 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// Account menu items struct.
var accountMenuItems = []string {
"order history",
"faq",
"about",
}
// Coffee represents a coffee product
type Coffee struct {
Name string
RoastType string
Ounces int
BeanType string
Price float64
Color string // Hex color for background
Description string
}
// CartItem represents an item in the cart
type CartItem struct {
Coffee Coffee
Quantity int
}
type model struct {
username string
coffees []Coffee
cursor int
cart map[int]*CartItem // maps coffee index to cart item
cartCursor int // cursor position in cart view
accountCursor int
accountPage string
windowWidth int
windowHeight int
viewingCart bool // true when viewing cart details
viewingAccount bool
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.windowWidth = msg.Width
m.windowHeight = msg.Height
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "s":
m.viewingCart = false
m.viewingAccount = false
m.accountPage = ""
case "c":
m.viewingCart = true
m.viewingAccount = false
m.accountPage = ""
case "a":
m.viewingCart = false
m.viewingAccount = true
case "up", "k":
if m.viewingAccount && m.accountPage == "" {
if m.accountCursor > 0 {
m.accountCursor--
}
} else if !m.viewingCart && !m.viewingAccount && m.cursor > 0 {
m.cursor--
} else if m.viewingCart && m.cartCursor > 0 {
m.cartCursor--
}
case "down", "j":
if m.viewingAccount && m.accountPage == "" {
if m.accountCursor < len(accountMenuItems)-1 {
m.accountCursor++
}
} else if !m.viewingCart && !m.viewingAccount && m.cursor < len(m.coffees)-1 {
m.cursor++
} else if m.viewingCart && m.cartCursor < len(m.cart)-1 {
m.cartCursor++
}
case "+", "=":
if !m.viewingCart {
// Increment quantity in shop view
if item, exists := m.cart[m.cursor]; exists {
item.Quantity++
} else {
m.cart[m.cursor] = &CartItem{
Coffee: m.coffees[m.cursor],
Quantity: 1,
}
}
} else {
// Increment quantity in cart view
cartItems := m.getCartItemsSlice()
if m.cartCursor >= 0 && m.cartCursor < len(cartItems) {
cartItems[m.cartCursor].Quantity++
}
}
case "enter":
if m.viewingAccount && m.accountPage == "" {
switch accountMenuItems[m.accountCursor]{
case "order history":
m.accountPage = "orders"
case "faq":
m.accountPage = "faq"
case "about":
m.accountPage = "about"
}
}
case "esc":
if m.viewingAccount && m.accountPage != "" {
m.accountPage = ""
}
case "-", "_":
if !m.viewingCart {
// Decrement quantity in shop view
if item, exists := m.cart[m.cursor]; exists {
item.Quantity--
if item.Quantity <= 0 {
delete(m.cart, m.cursor)
// Reset cart cursor if we deleted the last item
if len(m.cart) == 0 {
m.cartCursor = 0
} else if m.cartCursor >= len(m.cart) {
m.cartCursor = len(m.cart) - 1
}
}
}
} else {
// Decrement quantity in cart view
cartItems := m.getCartItemsSlice()
if m.cartCursor >= 0 && m.cartCursor < len(cartItems) {
cartItems[m.cartCursor].Quantity--
if cartItems[m.cartCursor].Quantity <= 0 {
// Find and delete this item from the cart
for idx, item := range m.cart {
if item == cartItems[m.cartCursor] {
delete(m.cart, idx)
break
}
}
// Reset cart cursor
if len(m.cart) == 0 {
m.cartCursor = 0
} else if m.cartCursor >= len(m.cart) {
m.cartCursor = len(m.cart) - 1
}
}
}
}
}
}
return m, nil
}
func (m model) View() string {
if m.windowWidth == 0 {
m.windowWidth = 120
m.windowHeight = 30
}
// Build header with cart tab
header := m.buildHeader()
// Build main content based on view
var content string
if m.viewingAccount {
content = m.buildAccountView()
} else if m.viewingCart {
content = m.buildCartView()
} else {
content = m.buildShopView()
}
// Add vertical spacing around content
contentWithPadding := lipgloss.NewStyle().
MarginTop(1).
MarginBottom(4).
Render(content)
// Build footer
footer := m.buildFooter()
// Combine all app content
appContent := lipgloss.JoinVertical(
lipgloss.Left,
header,
contentWithPadding,
footer,
)
// Calculate vertical centering
appHeight := lipgloss.Height(appContent)
verticalPadding := 2 // Minimum padding from top and bottom
availableSpace := m.windowHeight - appHeight - (verticalPadding * 2)
if availableSpace < 0 {
availableSpace = 0
verticalPadding = 0
}
// Split remaining space between top and bottom
topPadding := verticalPadding + (availableSpace / 2)
// Create top spacing
topSpacing := ""
for i := 0; i < topPadding; i++ {
topSpacing += "\n"
}
return topSpacing + appContent
}
func (m model) buildHeader() string {
// Calculate cart total and item count
total := 0.0
itemCount := 0
for _, item := range m.cart {
total += float64(item.Quantity) * item.Coffee.Price
itemCount += item.Quantity
}
// Define colors
whiteStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF"))
grayStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#AAAAAA"))
boldWhiteStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).Bold(true)
// Build shop tab
var shopKeybind, shopName string
if !m.viewingCart {
// Active: bold white keybind and name
shopKeybind = boldWhiteStyle.Render("s")
shopName = boldWhiteStyle.Render("shop")
} else {
// Inactive: bold gray keybind, gray name
shopKeybind = lipgloss.NewStyle().Foreground(lipgloss.Color("#AAAAAA")).Bold(true).Render("s")
shopName = grayStyle.Render("shop")
}
// Build cart tab
var cartKeybind, cartName, cartInfo string
itemCountText := grayStyle.Render(fmt.Sprintf("[%d]", itemCount))
if m.viewingCart {
// Active: bold white keybind and name
cartKeybind = boldWhiteStyle.Render("c")
cartName = boldWhiteStyle.Render("cart")
cartInfo = fmt.Sprintf(" %s %s",
boldWhiteStyle.Render(fmt.Sprintf("$%.2f", total)),
itemCountText)
} else {
// Inactive: bold gray keybind, gray name
boldGrayStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#AAAAAA")).Bold(true)
boldWhitePriceStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).Bold(true)
cartKeybind = boldGrayStyle.Render("c")
cartName = grayStyle.Render("cart")
cartInfo = fmt.Sprintf(" %s %s",
boldWhitePriceStyle.Render(fmt.Sprintf("$%.2f", total)),
itemCountText)
}
// Build Account Tabs
var accountKeybind, accountName string
if m.viewingAccount {
accountKeybind = boldWhiteStyle.Render("a")
accountName = boldWhiteStyle.Render("account")
} else {
accountKeybind = lipgloss.NewStyle().Foreground(lipgloss.Color("#AAAAAA")).Bold(true).Render("a")
accountName = grayStyle.Render("account")
}
// Build the tabs with separator only in the middle
separator := whiteStyle.Render("|")
tabsContent := fmt.Sprintf("%s %s %s %s %s%s %s %s %s",
shopKeybind,
shopName,
separator,
cartKeybind,
cartName,
cartInfo,
separator,
accountKeybind,
accountName,
)
// Add box with padding (no vertical padding, just horizontal)
tabBox := lipgloss.NewStyle().
Border(lipgloss.NormalBorder()).
Padding(0, 2).
MarginBottom(1)
// Center the whole thing
centered := lipgloss.NewStyle().
Width(m.windowWidth).
Align(lipgloss.Center)
return centered.Render(tabBox.Render(tabsContent))
}
func (m model) buildShopView() string {
leftWidth := 18 // Narrower list
leftMargin := 4 // Minimal left margin
rightWidth := m.windowWidth - leftWidth - leftMargin - 4
// Build the left panel (coffee list)
leftPanel := ""
for i, coffee := range m.coffees {
// Apply background color when selected, white text
if m.cursor == i {
style := lipgloss.NewStyle().
Background(lipgloss.Color(coffee.Color)).
Foreground(lipgloss.Color("#FFFFFF")).
Padding(0, 1).
Width(leftWidth - 2).
Align(lipgloss.Left)
leftPanel += style.Render(coffee.Name) + "\n"
} else {
style := lipgloss.NewStyle().
Foreground(lipgloss.Color("#AAAAAA")).
Padding(0, 1).
Width(leftWidth - 2).
Align(lipgloss.Left)
leftPanel += style.Render(coffee.Name) + "\n"
}
}
// Add left margin to the list
leftContainer := lipgloss.NewStyle().
Width(leftWidth).
MarginLeft(leftMargin)
// 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)
descStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFFFF")).
Width(rightWidth - 4)
quantityStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFFFF")).
Bold(true)
// Get current quantity
quantity := 0
if item, exists := m.cart[m.cursor]; 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)
// Layout the panels side by side
mainContent := lipgloss.JoinHorizontal(
lipgloss.Top,
leftContainer.Render(leftPanel),
detailContainer.Render(detailView),
)
return mainContent
}
func (m model) buildCartView() string {
if len(m.cart) == 0 {
emptyStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#666666")).
Align(lipgloss.Center).
Width(m.windowWidth).
Padding(4, 0)
return emptyStyle.Render("Your cart is empty\n\nPress s to go back to shop")
}
cartItems := ""
boxWidth := m.windowWidth - 20 // Leave some margin
itemSlice := m.getCartItemsSlice()
for idx, item := range itemSlice {
itemTotal := float64(item.Quantity) * item.Coffee.Price
// Left side: name and info
nameStyle := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FFFFFF"))
infoStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#AAAAAA"))
// Right side: quantity controls and price
quantityStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFFFF"))
priceStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFFFF")).
Bold(true)
// Build content with proper spacing
nameText := nameStyle.Render(item.Coffee.Name)
infoText := infoStyle.Render(fmt.Sprintf("%s | %doz | %s",
item.Coffee.RoastType,
item.Coffee.Ounces,
item.Coffee.BeanType))
quantityText := quantityStyle.Render(fmt.Sprintf("- %d +", item.Quantity))
priceText := priceStyle.Render(fmt.Sprintf("$%.2f", itemTotal))
// Calculate spacing
nameWidth := lipgloss.Width(nameText)
quantityWidth := lipgloss.Width(quantityText)
priceWidth := lipgloss.Width(priceText)
spacing := boxWidth - nameWidth - quantityWidth - priceWidth - 8 // 8 for padding and gaps
if spacing < 1 {
spacing = 1
}
contentLine1 := nameText +
lipgloss.NewStyle().Width(spacing).Render("") +
quantityText + " " + priceText
contentLine2 := infoText
boxContent := contentLine1 + "\n" + contentLine2
// Create box for this item
itemBox := lipgloss.NewStyle().
Border(lipgloss.NormalBorder()).
Padding(0, 2).
Width(boxWidth).
Foreground(lipgloss.Color("#FFFFFF"))
// Center the box
centered := lipgloss.NewStyle().
Width(m.windowWidth).
Align(lipgloss.Center)
cartItems += centered.Render(itemBox.Render(boxContent))
// Add spacing between items
if idx < len(itemSlice)-1 {
cartItems += "\n\n"
}
}
return cartItems
}
func (m model) buildAccountView() string {
// If we're on a subpage, show that content
if m.accountPage != "" {
return m.buildAccountSubpage()
}
// Otherwise show the account menu
leftWidth := 18
leftMargin := 4
leftPanel := ""
for i, item := range accountMenuItems {
// Apply background color when selected, white text
if m.accountCursor == i {
style := lipgloss.NewStyle().
Background(lipgloss.Color("#4682B4")).
Foreground(lipgloss.Color("#FFFFFF")).
Padding(0, 1).
Width(leftWidth - 2).
Align(lipgloss.Left)
leftPanel += style.Render(item) + "\n"
} else {
style := lipgloss.NewStyle().
Foreground(lipgloss.Color("#AAAAAA")).
Padding(0, 1).
Width(leftWidth - 2).
Align(lipgloss.Left)
leftPanel += style.Render(item) + "\n"
}
}
leftContainer := lipgloss.NewStyle().
Width(leftWidth).
MarginLeft(leftMargin)
return leftContainer.Render(leftPanel)
}
func (m model) buildAccountSubpage() string {
titleStyle := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FFFFFF")).
MarginBottom(2)
contentStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#AAAAAA")).
Width(m.windowWidth - 20).
MarginLeft(4)
var content string
switch m.accountPage {
case "orders":
content = titleStyle.Render("Order History") + "\n\n" +
contentStyle.Render("No orders yet. Your order history will appear here once you make a purchase.")
case "faq":
content = titleStyle.Render("FAQ") + "\n\n" +
contentStyle.Render("Q: How do I place an order?\nA: Browse products with j/k, adjust quantity with +/-, and checkout via the cart.\n\nQ: What payment methods do you accept?\nA: We accept all major credit cards via Stripe.\n\nPress ESC to go back.")
case "about":
content = titleStyle.Render("About Terminal Coffee Shop") + "\n\n" +
contentStyle.Render("A terminal-based coffee ordering experience.\nBuilt with Go and Bubbletea.\n\nPress ESC to go back.")
default:
content = "Unknown page"
}
return content
}
// getCartItemsSlice converts the cart map to a sorted slice for consistent iteration
func (m model) getCartItemsSlice() []*CartItem {
items := make([]*CartItem, 0, len(m.cart))
for _, item := range m.cart {
items = append(items, item)
}
return items
}
func (m model) buildFooter() string {
// Style for keybinds (bold)
keybindStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#666666")).
Bold(true)
// Style for descriptions (normal)
descStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#666666"))
var footerText string
if m.viewingAccount {
if m.accountPage != "" {
// In a subpage
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s",
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 {
// In account menu
footerText = fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s",
keybindStyle.Render("j/k"),
descStyle.Render("menu"),
keybindStyle.Render("enter"),
descStyle.Render("select"),
keybindStyle.Render("s"),
descStyle.Render("shop"),
keybindStyle.Render("c"),
descStyle.Render("cart"),
keybindStyle.Render("q"),
descStyle.Render("quit"),
)
}
} else if m.viewingCart {
footerText = fmt.Sprintf("%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("q"),
descStyle.Render("quit"),
)
} else {
footerText = fmt.Sprintf("%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("q"),
descStyle.Render("quit"),
)
}
return lipgloss.NewStyle().
Align(lipgloss.Center).
Width(m.windowWidth).
Render(footerText)
}
// InitialModel creates a new model with default coffee options
func InitialModel(username string) model {
return model{
username: username,
coffees: []Coffee{
{
Name: "Espresso",
RoastType: "Dark Roast",
Ounces: 2,
BeanType: "Arabica",
Price: 3.50,
Color: "#8B4513",
Description: "A bold, concentrated shot of pure coffee bliss. Perfect for those who need an immediate caffeine injection to survive the day.",
},
{
Name: "Latte",
RoastType: "Medium Roast",
Ounces: 12,
BeanType: "Arabica Blend",
Price: 5.00,
Color: "#D2691E",
Description: "Smooth espresso paired with steamed milk and a light layer of foam. For when you want coffee but also want to feel fancy about it.",
},
{
Name: "Cappuccino",
RoastType: "Medium Roast",
Ounces: 8,
BeanType: "Italian Blend",
Price: 4.50,
Color: "#CD853F",
Description: "Equal parts espresso, steamed milk, and foam. The classic Italian choice for people who know what they're doing.",
},
{
Name: "Americano",
RoastType: "Dark Roast",
Ounces: 16,
BeanType: "Arabica",
Price: 4.00,
Color: "#A0522D",
Description: "Espresso with hot water. Simple, strong, and no-nonsense. This is coffee for people who actually like the taste of coffee.",
},
{
Name: "Cold Brew",
RoastType: "Medium Roast",
Ounces: 16,
BeanType: "Colombian",
Price: 5.50,
Color: "#4682B4",
Description: "Steeped for 12 hours to create a smooth, less acidic coffee experience. Served cold because sometimes hot coffee is just too much effort.",
},
{
Name: "Macchiato",
RoastType: "Dark Roast",
Ounces: 3,
BeanType: "Robusta Blend",
Price: 4.25,
Color: "#DAA520",
Description: "Espresso 'marked' with a dollop of foamed milk. Small but mighty, like a tiny caffeinated warrior.",
},
},
cursor: 0,
cart: make(map[int]*CartItem),
cartCursor: 0,
accountCursor: 0,
accountPage: "",
windowWidth: 120,
windowHeight: 30,
viewingCart: false,
viewingAccount: false,
}
}

View File

@@ -108,7 +108,7 @@ func TestSeed(t *testing.T) {
t.Errorf("Unexpected product name: %s", p.Name)
}
if p.Price <= 0 {
t.Errorf("Product %s has invalid price: %f", p.Name, p.Price)
t.Errorf("Product %s has invalid price: %d", p.Name, p.Price)
}
}
}

View File

@@ -27,7 +27,7 @@ func Seed(db *gorm.DB) error {
RoastType: "Dark Roast",
Ounces: 2,
BeanType: "Arabica",
Price: 3.50,
Price: 350, // $3.50
Color: "#8B4513",
Description: "Bold and intense coffee with a rich crema",
},
@@ -36,7 +36,7 @@ func Seed(db *gorm.DB) error {
RoastType: "Medium Roast",
Ounces: 8,
BeanType: "Arabica",
Price: 4.00,
Price: 400, // $4.00
Color: "#A0522D",
Description: "Smooth and balanced espresso diluted with hot water",
},
@@ -45,7 +45,7 @@ func Seed(db *gorm.DB) error {
RoastType: "Medium Roast",
Ounces: 6,
BeanType: "Arabica",
Price: 4.50,
Price: 450, // $4.50
Color: "#C4A574",
Description: "Espresso with steamed milk and a thick layer of foam",
},
@@ -54,7 +54,7 @@ func Seed(db *gorm.DB) error {
RoastType: "Medium Roast",
Ounces: 12,
BeanType: "Arabica",
Price: 5.00,
Price: 500, // $5.00
Color: "#D2B48C",
Description: "Creamy espresso with plenty of steamed milk and a touch of foam",
},
@@ -63,7 +63,7 @@ func Seed(db *gorm.DB) error {
RoastType: "Dark Roast",
Ounces: 12,
BeanType: "Arabica",
Price: 5.50,
Price: 550, // $5.50
Color: "#8B4513",
Description: "Chocolate and espresso combined with steamed milk. Sweet and indulgent.",
},
@@ -72,7 +72,7 @@ func Seed(db *gorm.DB) error {
RoastType: "Dark Roast",
Ounces: 3,
BeanType: "Robusta Blend",
Price: 4.25,
Price: 425, // $4.25
Color: "#DAA520",
Description: "Espresso 'marked' with a dollop of foamed milk. Small but mighty, like a tiny caffeinated warrior.",
},

View File

@@ -13,7 +13,7 @@ type Coffee struct {
RoastType string `gorm:"size:100" json:"roast_type"`
Ounces int `json:"ounces"`
BeanType string `gorm:"size:100" json:"bean_type"`
Price float64 `gorm:"not null" json:"price"`
Price int `gorm:"not null" json:"price"`
Color string `gorm:"size:7" json:"color"` // Hex color for background
Description string `gorm:"type:text" json:"description"`
CreatedAt time.Time `json:"created_at"`
@@ -26,7 +26,6 @@ func (Coffee) TableName() string {
return "coffees"
}
// AccountMenuItems something
var AccountMenuItems = []string{
"order history",

View File

@@ -27,7 +27,7 @@ func (m Model) BuildCartView() string {
}
for idx, item := range itemSlice {
itemTotal := float64(item.Quantity) * item.Coffee.Price
itemTotal := item.Quantity * item.Coffee.Price
// Left side: name and individual price
nameStyle := lipgloss.NewStyle().
@@ -51,13 +51,13 @@ func (m Model) BuildCartView() string {
// Build content with proper spacing
nameText := nameStyle.Render(item.Coffee.Name)
individualPriceText := individualPriceStyle.Render(fmt.Sprintf(" $%.2f", item.Coffee.Price))
individualPriceText := individualPriceStyle.Render(fmt.Sprintf(" $%.2f", float64(item.Coffee.Price)/100))
infoText := infoStyle.Render(fmt.Sprintf("%s | %doz | %s",
item.Coffee.RoastType,
item.Coffee.Ounces,
item.Coffee.BeanType))
quantityText := quantityStyle.Render(fmt.Sprintf("- %d +", item.Quantity))
totalPriceText := totalPriceStyle.Render(fmt.Sprintf("$%.2f", itemTotal))
totalPriceText := totalPriceStyle.Render(fmt.Sprintf("$%.2f", float64(itemTotal)/100))
// Calculate spacing
nameAndPriceWidth := lipgloss.Width(nameText) + lipgloss.Width(individualPriceText)

View File

@@ -45,15 +45,15 @@ func (m Model) RenderConfirmation() string {
// Cart summary
cartSection := ""
total := 0.0
total := 0
items := m.GetCartItemsSlice()
for _, item := range items {
line := fmt.Sprintf(" %s x%d $%.2f", item.Coffee.Name, item.Quantity, item.Coffee.Price*float64(item.Quantity))
line := fmt.Sprintf(" %s x%d $%.2f", item.Coffee.Name, item.Quantity, float64(item.Coffee.Price * item.Quantity)/100)
cartSection += sectionStyle.Render(line) + "\n"
total += item.Coffee.Price * float64(item.Quantity)
total += item.Coffee.Price * item.Quantity
}
cartSection += sectionStyle.Render(
labelStyle.Render("Total: ") + valueStyle.Render(fmt.Sprintf("$%.2f", total)),
labelStyle.Render("Total: ") + valueStyle.Render(fmt.Sprintf("$%.2f", float64(total)/100)),
)
success := successStyle.Render("Order placed successfully!")

View File

@@ -8,10 +8,10 @@ import (
func (m Model) BuildHeader() string {
// Calculate cart total and item count
total := 0.0
total := 0
itemCount := 0
for _, item := range m.Cart {
total += float64(item.Quantity) * item.Coffee.Price
total += item.Quantity * item.Coffee.Price
itemCount += item.Quantity
}
@@ -29,14 +29,14 @@ func (m Model) BuildHeader() string {
cartKeybind = boldWhiteStyle.Render("c")
cartName = boldWhiteStyle.Render("cart")
cartInfo = fmt.Sprintf(" %s %s",
boldWhiteStyle.Render(fmt.Sprintf("$%.2f", total)),
boldWhiteStyle.Render(fmt.Sprintf("$%.2f", float64(total)/100)),
itemCountText)
} else {
// Inactive: bold gray keybind, gray name
cartKeybind = boldGrayStyle.Render("c")
cartName = grayStyle.Render("cart")
cartInfo = fmt.Sprintf(" %s %s",
boldWhiteStyle.Render(fmt.Sprintf("$%.2f", total)),
boldWhiteStyle.Render(fmt.Sprintf("$%.2f", float64(total)/100)),
itemCountText)
}
cartTab := cartKeybind + " " + cartName + cartInfo

View File

@@ -35,13 +35,13 @@ func (m Model) BuildMenuView() string {
hint := base.Render("press esc to close")
itemCount := 0
total := 0.0
total := 0
for _, item := range m.Cart {
total += float64(item.Quantity) * item.Coffee.Price
total += item.Quantity * item.Coffee.Price
itemCount += item.Quantity
}
cartHint := base.Render(fmt.Sprintf("cart $%.2f [%d]", total, itemCount))
cartHint := base.Render(fmt.Sprintf("cart $%.2f [%d]", float64(total)/100, itemCount))
assembled := lipgloss.JoinVertical(
lipgloss.Center,

View File

@@ -1433,7 +1433,7 @@ func NewModel(username string) Model {
RoastType: "Dark Roast",
Ounces: 2,
BeanType: "Arabica",
Price: 3.50,
Price: 350, // $3.50
Color: "#8B4513",
Description: "A bold, concentrated shot of pure coffee bliss. Perfect for those who need an immediate caffeine injection to survive the day.",
},
@@ -1442,7 +1442,7 @@ func NewModel(username string) Model {
RoastType: "Medium Roast",
Ounces: 12,
BeanType: "Arabica Blend",
Price: 5.00,
Price: 500, // $5.00
Color: "#D2691E",
Description: "Smooth espresso paired with steamed milk and a light layer of foam. For when you want coffee but also want to feel fancy about it.",
},
@@ -1451,7 +1451,7 @@ func NewModel(username string) Model {
RoastType: "Medium Roast",
Ounces: 8,
BeanType: "Italian Blend",
Price: 4.50,
Price: 450, // $4.50
Color: "#CD853F",
Description: "Equal parts espresso, steamed milk, and foam. The classic Italian choice for people who know what they're doing.",
},
@@ -1460,7 +1460,7 @@ func NewModel(username string) Model {
RoastType: "Dark Roast",
Ounces: 16,
BeanType: "Arabica",
Price: 4.00,
Price: 400, // $4.00
Color: "#A0522D",
Description: "Espresso with hot water. Simple, strong, and no-nonsense. This is coffee for people who actually like the taste of coffee.",
},
@@ -1469,7 +1469,7 @@ func NewModel(username string) Model {
RoastType: "Medium Roast",
Ounces: 16,
BeanType: "Colombian",
Price: 5.50,
Price: 550, // $5.50
Color: "#4682B4",
Description: "Some really good Mocha",
},
@@ -1478,7 +1478,7 @@ func NewModel(username string) Model {
RoastType: "Dark Roast",
Ounces: 3,
BeanType: "Robusta Blend",
Price: 4.25,
Price: 425, // $4.25
Color: "#DAA520",
Description: "Espresso 'marked' with a dollop of foamed milk. Small but mighty, like a tiny caffeinated warrior.",
},

View File

@@ -88,7 +88,7 @@ func (m Model) BuildShopView() string {
selectedCoffee.RoastType,
selectedCoffee.Ounces,
selectedCoffee.BeanType)) + "\n\n"
detailView += priceStyle.Render(fmt.Sprintf("$%.2f", selectedCoffee.Price)) + "\n\n"
detailView += priceStyle.Render(fmt.Sprintf("$%.2f", float64(selectedCoffee.Price)/100)) + "\n\n"
detailView += descStyle.Render(selectedCoffee.Description) + "\n\n"
detailView += quantityStyle.Render(fmt.Sprintf("- %d +", quantity)) + "\n"
}