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>
199 lines
5.0 KiB
Go
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()
|
|
}
|