forked from charmbracelet/bubbles
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Example: Credit Card Input Form (charmbracelet#338)
* feat(cc): Add Credit Card Input Form Example and `ValidatorFuncs` to ensure credit cards are valid
- Loading branch information
1 parent
21de41a
commit d56d8ae
Showing
3 changed files
with
206 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
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.Start(); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
type tickMsg struct{} | ||
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) 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 | ||
} else { | ||
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 { | ||
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 | ||
} | ||
} |
This file contains 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 characters
This file contains 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 characters