login.go

  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}