Last active
January 11, 2023 15:15
-
-
Save bashbunni/fed91563900a9f6e20cde881fe68ac31 to your computer and use it in GitHub Desktop.
Revisions
-
bashbunni revised this gist
Jan 11, 2023 . 1 changed file with 37 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,37 @@ 131a132,145 > func (m model) checkMinLen() error { > var err error > > c := m.inputs[m.focused] > if len(c.Value()) != c.CharLimit { > err = fmt.Errorf( > "%s should be at least %d characters", > c.Value(), > c.CharLimit, > ) > } > return err > } > 141a156 > m.err = m.checkMinLen() 167a183,201 > if m.err != nil { > return fmt.Sprintf( > ` Total: $21.50: > %s > %s > %s %s > %s %s > %s > `, > inputStyle.Width(30).Render("Card Number"), > m.inputs[ccn].View(), > inputStyle.Width(6).Render("EXP"), > inputStyle.Width(6).Render("CVV"), > m.inputs[exp].View(), > m.inputs[cvv].View(), > continueStyle.Render("Continue ->"), > ) + "\n" + > m.err.Error() + "\n" > } -
bashbunni created this gist
Jan 11, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,232 @@ package main import ( "fmt" "log" "strconv" "strings" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) func main() { p := tea.NewProgram(initialModel()) if _, err := p.Run(); err != nil { log.Fatal(err) } } type ( errMsg error ) const ( ccn = iota exp cvv ) const ( hotPink = lipgloss.Color("#FF06B7") darkGray = lipgloss.Color("#767676") ) var ( inputStyle = lipgloss.NewStyle().Foreground(hotPink) continueStyle = lipgloss.NewStyle().Foreground(darkGray) ) type model struct { inputs []textinput.Model focused int err error } // Validator functions to ensure valid input func ccnValidator(s string) error { // Credit Card Number should a string less than 20 digits // It should include 16 integers and 3 spaces if len(s) > 16+3 { return fmt.Errorf("CCN is too long") } // The last digit should be a number unless it is a multiple of 4 in which // case it should be a space if len(s)%5 == 0 && s[len(s)-1] != ' ' { return fmt.Errorf("CCN must separate groups with spaces") } if len(s)%5 != 0 && (s[len(s)-1] < '0' || s[len(s)-1] > '9') { return fmt.Errorf("CCN is invalid") } // The remaining digits should be integers c := strings.ReplaceAll(s, " ", "") _, err := strconv.ParseInt(c, 10, 64) return err } func expValidator(s string) error { // The 3 character should be a slash (/) // The rest thould be numbers e := strings.ReplaceAll(s, "/", "") _, err := strconv.ParseInt(e, 10, 64) if err != nil { return fmt.Errorf("EXP is invalid") } // There should be only one slash and it should be in the 2nd index (3rd character) if len(s) >= 3 && (strings.Index(s, "/") != 2 || strings.LastIndex(s, "/") != 2) { return fmt.Errorf("EXP is invalid") } return nil } func cvvValidator(s string) error { // The CVV should be a number of 3 digits // Since the input will already ensure that the CVV is a string of length 3, // All we need to do is check that it is a number _, err := strconv.ParseInt(s, 10, 64) return err } func initialModel() model { var inputs []textinput.Model = make([]textinput.Model, 3) inputs[ccn] = textinput.New() inputs[ccn].Placeholder = "4505 **** **** 1234" inputs[ccn].Focus() inputs[ccn].CharLimit = 20 inputs[ccn].Width = 30 inputs[ccn].Prompt = "" inputs[ccn].Validate = ccnValidator inputs[exp] = textinput.New() inputs[exp].Placeholder = "MM/YY " inputs[exp].CharLimit = 5 inputs[exp].Width = 5 inputs[exp].Prompt = "" inputs[exp].Validate = expValidator inputs[cvv] = textinput.New() inputs[cvv].Placeholder = "XXX" inputs[cvv].CharLimit = 3 inputs[cvv].Width = 5 inputs[cvv].Prompt = "" inputs[cvv].Validate = cvvValidator return model{ inputs: inputs, focused: 0, err: nil, } } func (m model) Init() tea.Cmd { return textinput.Blink } func (m model) checkMinLen() error { var err error c := m.inputs[m.focused] if len(c.Value()) != c.CharLimit { err = fmt.Errorf( "%s should be at least %d characters", c.Value(), c.CharLimit, ) } return err } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs)) switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { case tea.KeyEnter: if m.focused == len(m.inputs)-1 { return m, tea.Quit } m.err = m.checkMinLen() m.nextInput() case tea.KeyCtrlC, tea.KeyEsc: return m, tea.Quit case tea.KeyShiftTab, tea.KeyCtrlP: m.prevInput() case tea.KeyTab, tea.KeyCtrlN: m.nextInput() } for i := range m.inputs { m.inputs[i].Blur() } m.inputs[m.focused].Focus() // We handle errors just like any other message case errMsg: m.err = msg return m, nil } for i := range m.inputs { m.inputs[i], cmds[i] = m.inputs[i].Update(msg) } return m, tea.Batch(cmds...) } func (m model) View() string { if m.err != nil { return fmt.Sprintf( ` Total: $21.50: %s %s %s %s %s %s %s `, inputStyle.Width(30).Render("Card Number"), m.inputs[ccn].View(), inputStyle.Width(6).Render("EXP"), inputStyle.Width(6).Render("CVV"), m.inputs[exp].View(), m.inputs[cvv].View(), continueStyle.Render("Continue ->"), ) + "\n" + m.err.Error() + "\n" } return fmt.Sprintf( ` Total: $21.50: %s %s %s %s %s %s %s `, inputStyle.Width(30).Render("Card Number"), m.inputs[ccn].View(), inputStyle.Width(6).Render("EXP"), inputStyle.Width(6).Render("CVV"), m.inputs[exp].View(), m.inputs[cvv].View(), continueStyle.Render("Continue ->"), ) + "\n" } // nextInput focuses the next input field func (m *model) nextInput() { m.focused = (m.focused + 1) % len(m.inputs) } // prevInput focuses the previous input field func (m *model) prevInput() { m.focused-- // Wrap around if m.focused < 0 { m.focused = len(m.inputs) - 1 } }