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, 3),
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 }
49 m.inputs[i] = t
50 }
51
52 return m
53}
54
55func (m loginModel) Init() tea.Cmd { return textinput.Blink }
56
57func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
58 switch msg := msg.(type) {
59 case tea.KeyMsg:
60 switch msg.String() {
61 case "ctrl+c", "esc":
62 return m, tea.Quit
63 case "tab", "shift+tab", "enter", "up", "down":
64 s := msg.String()
65 if s == "enter" && m.focusIndex == len(m.inputs) {
66 m.config = &config.Config{
67 ServiceProvider: m.inputs[0].Value(),
68 Email: m.inputs[1].Value(),
69 Password: m.inputs[2].Value(),
70 }
71 if err := config.SaveConfig(m.config); err != nil {
72 m.err = err
73 return m, nil
74 }
75 return m, tea.Quit
76 }
77 if s == "up" || s == "shift+tab" {
78 m.focusIndex--
79 } else {
80 m.focusIndex++
81 }
82 if m.focusIndex > len(m.inputs) {
83 m.focusIndex = 0
84 } else if m.focusIndex < 0 {
85 m.focusIndex = len(m.inputs)
86 }
87 cmds := make([]tea.Cmd, len(m.inputs))
88 for i := 0; i < len(m.inputs); i++ {
89 if i == m.focusIndex {
90 cmds[i] = m.inputs[i].Focus()
91 m.inputs[i].Prompt = "> "
92 continue
93 }
94 m.inputs[i].Blur()
95 m.inputs[i].Prompt = " "
96 }
97 return m, tea.Batch(cmds...)
98 }
99 }
100 cmd := m.updateInputs(msg)
101 return m, cmd
102}
103
104func (m *loginModel) updateInputs(msg tea.Msg) tea.Cmd {
105 cmds := make([]tea.Cmd, len(m.inputs))
106 for i := range m.inputs {
107 m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
108 }
109 return tea.Batch(cmds...)
110}
111
112func (m loginModel) View() string {
113 var b strings.Builder
114 b.WriteString(InfoStyle.Render(" Welcome to email-cli! Please configure your email account. ") + "\n\n")
115 for i := range m.inputs {
116 b.WriteString(m.inputs[i].View())
117 if i < len(m.inputs)-1 {
118 b.WriteRune('\n')
119 }
120 }
121 button := "\n\n" + "[ Submit ]"
122 if m.focusIndex == len(m.inputs) {
123 button = "\n\n" + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("[ Submit ]")
124 }
125 b.WriteString(button)
126 if m.err != nil {
127 b.WriteString("\n\nError: " + m.err.Error())
128 }
129 b.WriteString(HelpStyle.Render("\n\n(tab to navigate, enter to submit, esc to quit)"))
130 return DialogBoxStyle.Render(b.String())
131}
132
133// RunLogin starts the login UI and returns the created config.
134func RunLogin() (*config.Config, error) {
135 p := tea.NewProgram(initialLoginModel())
136 m, err := p.Run()
137 if err != nil {
138 return nil, err
139 }
140 finalModel := m.(loginModel)
141 return finalModel.config, nil
142}