This repository has been archived on 2026-03-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
oubliette/internal/shell/banking/screen_transfer.go
Torjus Håkestad d226c32b9b fix: banking shell screen rendering artifacts and transfer panic
Fix rendering issues where content from previous screens bled through
when switching between views of different heights/widths:

- Pad every line to full terminal width (ANSI-aware) so shorter lines
  overwrite leftover content from previous renders
- Track terminal height via WindowSizeMsg and pad between content and
  footer to fill the screen
- Send tea.ClearScreen on all screen transitions for height changes
- Fix panic in transfer completion when routing number is < 4 chars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:50:34 +01:00

199 lines
5.0 KiB
Go

package banking
import (
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
const (
transferStepRouting = iota
transferStepDest
transferStepBeneficiary
transferStepBankName
transferStepAmount
transferStepMemo
transferStepConfirm
transferStepAuthCode
transferStepComplete
)
var transferPrompts = []string{
" ROUTING NUMBER (ABA): ",
" DESTINATION ACCOUNT: ",
" BENEFICIARY NAME: ",
" RECEIVING BANK NAME: ",
" AMOUNT (USD): ",
" MEMO / REFERENCE: ",
"",
" AUTHORIZATION CODE: ",
}
type transferModel struct {
step int
fields [8]string // indexed by step
transfer WireTransfer
confirm string // y/n input for confirm step
}
func newTransferModel() transferModel {
return transferModel{}
}
func (m transferModel) Update(msg tea.Msg) (transferModel, tea.Cmd) {
keyMsg, ok := msg.(tea.KeyMsg)
if !ok {
return m, nil
}
if m.step == transferStepComplete {
return m, nil
}
if m.step == transferStepConfirm {
switch keyMsg.Type {
case tea.KeyEnter:
switch strings.ToUpper(m.confirm) {
case "Y", "YES":
m.step = transferStepAuthCode
case "N", "NO":
m.confirm = "cancelled"
}
return m, nil
case tea.KeyBackspace:
if len(m.confirm) > 0 {
m.confirm = m.confirm[:len(m.confirm)-1]
}
default:
ch := keyMsg.String()
if len(ch) == 1 && ch[0] >= 32 && ch[0] < 127 && len(m.confirm) < 3 {
m.confirm += ch
}
}
return m, nil
}
switch keyMsg.Type {
case tea.KeyEnter:
val := strings.TrimSpace(m.fields[m.step])
if val == "" {
return m, nil
}
if m.step == transferStepAuthCode {
m.transfer.AuthCode = val
m.step = transferStepComplete
return m, nil
}
switch m.step {
case transferStepRouting:
m.transfer.RoutingNumber = val
case transferStepDest:
m.transfer.DestAccount = val
case transferStepBeneficiary:
m.transfer.Beneficiary = val
case transferStepBankName:
m.transfer.BankName = val
case transferStepAmount:
m.transfer.Amount = val
case transferStepMemo:
m.transfer.Memo = val
}
m.step++
return m, nil
case tea.KeyBackspace:
if len(m.fields[m.step]) > 0 {
m.fields[m.step] = m.fields[m.step][:len(m.fields[m.step])-1]
}
default:
ch := keyMsg.String()
if len(ch) == 1 && ch[0] >= 32 && ch[0] < 127 && len(m.fields[m.step]) < 40 {
m.fields[m.step] += ch
}
}
return m, nil
}
func (m transferModel) View() string {
var b strings.Builder
b.WriteString("\n")
b.WriteString(centerText("WIRE TRANSFER"))
b.WriteString("\n\n")
b.WriteString(thinDivider())
b.WriteString("\n\n")
// Show completed fields.
for i := 0; i < m.step && i < len(transferPrompts); i++ {
if i == transferStepConfirm {
continue
}
prompt := transferPrompts[i]
if prompt == "" {
continue
}
b.WriteString(baseStyle.Render(prompt))
b.WriteString(baseStyle.Render(m.fields[i]))
b.WriteString("\n")
}
// Current field.
switch {
case m.step == transferStepConfirm:
b.WriteString("\n")
b.WriteString(titleStyle.Render(" === TRANSFER SUMMARY ==="))
b.WriteString("\n\n")
b.WriteString(baseStyle.Render(fmt.Sprintf(" ROUTING: %s", m.transfer.RoutingNumber)))
b.WriteString("\n")
b.WriteString(baseStyle.Render(fmt.Sprintf(" ACCOUNT: %s", m.transfer.DestAccount)))
b.WriteString("\n")
b.WriteString(baseStyle.Render(fmt.Sprintf(" BENEFICIARY: %s", m.transfer.Beneficiary)))
b.WriteString("\n")
b.WriteString(baseStyle.Render(fmt.Sprintf(" BANK: %s", m.transfer.BankName)))
b.WriteString("\n")
b.WriteString(baseStyle.Render(fmt.Sprintf(" AMOUNT: $%s", m.transfer.Amount)))
b.WriteString("\n")
b.WriteString(baseStyle.Render(fmt.Sprintf(" MEMO: %s", m.transfer.Memo)))
b.WriteString("\n\n")
b.WriteString(titleStyle.Render(" CONFIRM TRANSFER? (Y/N): "))
b.WriteString(inputStyle.Render(m.confirm))
b.WriteString(inputStyle.Render("_"))
b.WriteString("\n")
case m.step == transferStepComplete:
b.WriteString("\n")
b.WriteString(thinDivider())
b.WriteString("\n\n")
b.WriteString(titleStyle.Render(" TRANSFER QUEUED FOR PROCESSING"))
b.WriteString("\n\n")
routing := m.transfer.RoutingNumber
if len(routing) > 4 {
routing = routing[:4]
}
b.WriteString(baseStyle.Render(fmt.Sprintf(" CONFIRMATION #: WR-%s-%s",
routing, "847291")))
b.WriteString("\n")
b.WriteString(baseStyle.Render(" STATUS: PENDING FEDWIRE SETTLEMENT"))
b.WriteString("\n")
b.WriteString(baseStyle.Render(" ESTIMATED COMPLETION: NEXT BUSINESS DAY"))
b.WriteString("\n\n")
b.WriteString(dimStyle.Render(" PRESS ANY KEY TO RETURN TO MAIN MENU"))
b.WriteString("\n")
case m.step < len(transferPrompts):
prompt := transferPrompts[m.step]
b.WriteString(titleStyle.Render(prompt))
b.WriteString(inputStyle.Render(m.fields[m.step]))
b.WriteString(inputStyle.Render("_"))
b.WriteString("\n")
}
if m.step < transferStepConfirm {
b.WriteString("\n")
b.WriteString(thinDivider())
b.WriteString("\n\n")
b.WriteString(dimStyle.Render(fmt.Sprintf(" STEP %d OF 6", m.step+1)))
b.WriteString("\n")
}
return b.String()
}