1package tui
2
3import (
4 "strings"
5
6 "github.com/andrinoff/email-cli/config"
7 "github.com/charmbracelet/bubbles/cursor"
8 "github.com/charmbracelet/bubbles/textinput"
9 tea "github.com/charmbracelet/bubbletea"
10 "github.com/charmbracelet/lipgloss"
11)
12
13type loginModel struct {
14 focusIndex int
15 inputs []textinput.Model
16 cursorMode cursor.Mode
17 config *config.Config
18 err error
19}
20
21func initialLoginModel() loginModel {
22 m := loginModel{
23 inputs: make([]textinput.Model, 4),
24 }
25
26 var t textinput.Model
27 for i := range m.inputs {
28 t = textinput.New()
29 t.Cursor.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
30 t.CharLimit = 32
31
32 switch i {
33 case 0:
34 t.Placeholder = "gmail or icloud"
35 t.Focus()
36 t.Prompt = "> "
37 t.CharLimit = 10
38 case 1:
39 t.Placeholder = "your@email.com"
40 t.Prompt = " "
41 t.CharLimit = 64
42 case 2:
43 t.Placeholder = "App-specific password"
44 t.Prompt = " "
45 t.EchoMode = textinput.EchoPassword
46 t.EchoCharacter = '•'
47 t.CharLimit = 64
48 case 3:
49 t.Placeholder = "Name"
50 t.Prompt = " "
51 t.CharLimit = 64
52 }
53 m.inputs[i] = t
54 }
55
56 return m
57}
58
59func (m loginModel) Init() tea.Cmd { return textinput.Blink }
60
61func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
62 switch msg := msg.(type) {
63 case tea.KeyMsg:
64 switch msg.String() {
65 case "ctrl+c", "esc":
66 return m, tea.Quit
67 case "tab", "shift+tab", "enter", "up", "down":
68 s := msg.String()
69 if s == "enter" && m.focusIndex == len(m.inputs) {
70 m.config = &config.Config{
71 ServiceProvider: m.inputs[0].Value(),
72 Email: m.inputs[1].Value(),
73 Password: m.inputs[2].Value(),
74 Name: m.inputs[3].Value(),
75 }
76 if err := config.SaveConfig(m.config); err != nil {
77 m.err = err
78 return m, nil
79 }
80 return m, tea.Quit
81 }
82 if s == "up" || s == "shift+tab" {
83 m.focusIndex--
84 } else {
85 m.focusIndex++
86 }
87 if m.focusIndex > len(m.inputs) {
88 m.focusIndex = 0
89 } else if m.focusIndex < 0 {
90 m.focusIndex = len(m.inputs)
91 }
92 cmds := make([]tea.Cmd, len(m.inputs))
93 for i := 0; i < len(m.inputs); i++ {
94 if i == m.focusIndex {
95 cmds[i] = m.inputs[i].Focus()
96 m.inputs[i].Prompt = "> "
97 continue
98 }
99 m.inputs[i].Blur()
100 m.inputs[i].Prompt = " "
101 }
102 return m, tea.Batch(cmds...)
103 }
104 }
105 cmd := m.updateInputs(msg)
106 return m, cmd
107}
108
109func (m *loginModel) updateInputs(msg tea.Msg) tea.Cmd {
110 cmds := make([]tea.Cmd, len(m.inputs))
111 for i := range m.inputs {
112 m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
113 }
114 return tea.Batch(cmds...)
115}
116
117func (m loginModel) View() string {
118 var b strings.Builder
119 b.WriteString(InfoStyle.Render(" Welcome to email-cli! Please configure your email account. ") + "\n\n")
120 for i := range m.inputs {
121 b.WriteString(m.inputs[i].View())
122 if i < len(m.inputs)-1 {
123 b.WriteRune('\n')
124 }
125 }
126 button := "\n\n" + "[ Submit ]"
127 if m.focusIndex == len(m.inputs) {
128 button = "\n\n" + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("[ Submit ]")
129 }
130 b.WriteString(button)
131 if m.err != nil {
132 b.WriteString("\n\nError: " + m.err.Error())
133 }
134 b.WriteString(HelpStyle.Render("\n\n(tab to navigate, enter to submit, esc to quit)"))
135 return DialogBoxStyle.Render(b.String())
136}
137
138// RunLogin starts the login UI and returns the created config.
139func RunLogin() (*config.Config, error) {
140 p := tea.NewProgram(initialLoginModel())
141 m, err := p.Run()
142 if err != nil {
143 return nil, err
144 }
145 finalModel := m.(loginModel)
146 return finalModel.config, nil
147}