main pico / pkg / tui / signup.go
Eric Bower  ·  2026-07-16
  1package tui
  2
  3import (
  4	"fmt"
  5	"net"
  6
  7	"git.sr.ht/~rockorager/vaxis"
  8	"git.sr.ht/~rockorager/vaxis/vxfw"
  9	"git.sr.ht/~rockorager/vaxis/vxfw/button"
 10	"git.sr.ht/~rockorager/vaxis/vxfw/richtext"
 11	"git.sr.ht/~rockorager/vaxis/vxfw/text"
 12	"github.com/picosh/pico/pkg/db"
 13	"github.com/picosh/pico/pkg/pssh"
 14	"github.com/picosh/pico/pkg/shared"
 15	"golang.org/x/crypto/ssh"
 16)
 17
 18type SignupPage struct {
 19	shared *SharedModel
 20	focus  string
 21	err    error
 22
 23	input *TextInput
 24	btn   *button.Button
 25}
 26
 27func NewSignupPage(shrd *SharedModel) *SignupPage {
 28	btn := button.New("SIGNUP", func() (vxfw.Command, error) { return nil, nil })
 29	btn.Style = button.StyleSet{
 30		Default: vaxis.Style{Background: grey},
 31		Focus:   vaxis.Style{Background: oj, Foreground: black},
 32	}
 33	input := NewTextInput("signup")
 34	return &SignupPage{shared: shrd, btn: btn, input: input}
 35}
 36
 37func (m *SignupPage) Footer() []Shortcut {
 38	return []Shortcut{
 39		{Shortcut: "tab", Text: "focus"},
 40		{Shortcut: "enter", Text: "create user"},
 41	}
 42}
 43
 44func (m *SignupPage) createAccount(name string) (*db.User, error) {
 45	if name == "" {
 46		return nil, fmt.Errorf("name cannot be empty")
 47	}
 48	key := shared.KeyForKeyText(m.shared.Session.PublicKey())
 49	remoteAddr := m.shared.Session.Conn.RemoteAddr().String()
 50	userIp, _, _ := net.SplitHostPort(remoteAddr)
 51	return m.shared.Dbpool.RegisterUser(name, key, "", userIp)
 52}
 53
 54func (m *SignupPage) CaptureEvent(ev vaxis.Event) (vxfw.Command, error) {
 55	switch msg := ev.(type) {
 56	case vaxis.Key:
 57		if msg.Matches(vaxis.KeyEnter) {
 58			user, err := m.createAccount(m.input.GetValue())
 59			if err != nil {
 60				m.err = err
 61				return vxfw.RedrawCmd{}, nil
 62			}
 63
 64			m.shared.User = user
 65
 66			pssh.SetUser(m.shared.Session, user)
 67
 68			m.shared.Logger = m.shared.Logger.With(
 69				"user", user.Name,
 70				"userId", user.ID,
 71			)
 72
 73			pssh.SetLogger(m.shared.Session, m.shared.Logger)
 74
 75			m.shared.App.PostEvent(Navigate{To: HOME})
 76		}
 77	}
 78	return nil, nil
 79}
 80
 81func (m *SignupPage) HandleEvent(ev vaxis.Event, phase vxfw.EventPhase) (vxfw.Command, error) {
 82	switch msg := ev.(type) {
 83	case PageIn:
 84		return m.input.FocusIn()
 85	case vaxis.Key:
 86		if msg.Matches(vaxis.KeyTab) {
 87			if m.focus == "button" {
 88				m.focus = "input"
 89				return m.input.FocusIn()
 90			}
 91			m.focus = "button"
 92			cmd, _ := m.input.FocusOut()
 93			return vxfw.BatchCmd([]vxfw.Command{
 94				cmd,
 95				vxfw.FocusWidgetCmd(m.btn),
 96			}), nil
 97		}
 98	}
 99
100	return nil, nil
101}
102
103func (m *SignupPage) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
104	w := ctx.Max.Width
105	h := ctx.Max.Height
106
107	root := vxfw.NewSurface(w, h, m)
108
109	fp := ssh.FingerprintSHA256(m.shared.Session.PublicKey())
110	intro := richtext.New([]vaxis.Segment{
111		{Text: "Welcome to pico.sh's management TUI!\n\n"},
112		{Text: "By creating an account you get access to our pico services.  We have free and paid services."},
113		{Text: "  After you create an account, you can go to our docs site to get started:\n\n  "},
114		{Text: "https://pico.sh/getting-started\n\n", Style: vaxis.Style{Hyperlink: "https://pico.sh/getting-started"}},
115		{Text: fmt.Sprintf("pubkey: %s\n\n", fp), Style: vaxis.Style{Foreground: purp}},
116	})
117	introSurf, _ := intro.Draw(ctx)
118	ah := 0
119	root.AddChild(0, ah, introSurf)
120	ah += int(introSurf.Size.Height)
121
122	inpSurf, _ := m.input.Draw(vxfw.DrawContext{
123		Characters: ctx.Characters,
124		Max:        vxfw.Size{Width: ctx.Max.Width, Height: 4},
125	})
126	root.AddChild(0, ah, inpSurf)
127	ah += int(inpSurf.Size.Height)
128
129	btnSurf, _ := m.btn.Draw(vxfw.DrawContext{
130		Characters: ctx.Characters,
131		Max:        vxfw.Size{Width: 10, Height: 1},
132	})
133	root.AddChild(0, ah, btnSurf)
134	ah += int(btnSurf.Size.Height)
135
136	if m.err != nil {
137		errTxt := text.New(m.err.Error())
138		errTxt.Style = vaxis.Style{Foreground: red}
139		errSurf, _ := errTxt.Draw(ctx)
140		root.AddChild(0, ah, errSurf)
141	}
142
143	return root, nil
144}