Eric Bower
·
18 Dec 24
plus.go
1package plus
2
3import (
4 "fmt"
5 "net/url"
6
7 "github.com/charmbracelet/bubbles/viewport"
8 tea "github.com/charmbracelet/bubbletea"
9 "github.com/charmbracelet/glamour"
10 "github.com/picosh/pico/tui/common"
11 "github.com/picosh/pico/tui/pages"
12)
13
14func PlusView(username string, w int) string {
15 paymentLink := "https://auth.pico.sh/checkout"
16 url := fmt.Sprintf("%s/%s", paymentLink, url.QueryEscape(username))
17 md := fmt.Sprintf(`Signup for pico+
18
19## $2/month (billed annually)
20
21- tuns
22 - per-site analytics
23- pages
24 - per-site analytics
25- prose
26 - no inherent storage limits
27 - blog analytics
28- 10GB total storage
29
30There are a few ways to purchase a membership. We try our best to
31provide immediate access to pico+ regardless of payment
32method.
33
34## Online Payment (credit card, paypal)
35
36%s
37
38## Snail Mail
39
40Send cash (USD) or check to:
41- pico.sh LLC
42- 206 E Huron St
43- Ann Arbor MI 48104
44
45## Notes
46
47You can keep purchasing pico+ to add additional years to your subscription.
48
49Have any questions not covered here? [Email](mailto:hello@pico.sh)
50us or join [IRC](https://pico.sh/irc), we will promptly respond.
51
52We do not maintain active subscriptions for pico+.
53Every year you must pay again. We do not take monthly payments, you
54must pay for a year up-front. Pricing is subject to change because
55we plan on continuing to include more services as we build them.`, url)
56
57 r, _ := glamour.NewTermRenderer(
58 // detect background color and pick either the default dark or light theme
59 glamour.WithAutoStyle(),
60 glamour.WithWordWrap(w-20),
61 )
62 out, _ := r.Render(md)
63 return out
64}
65
66// Model holds the state of the username UI.
67type Model struct {
68 shared *common.SharedModel
69 viewport viewport.Model
70}
71
72func headerHeight(shrd *common.SharedModel) int {
73 return shrd.HeaderHeight
74}
75
76func headerWidth(w int) int {
77 return w - 2
78}
79
80// NewModel returns a new username model in its initial state.
81func NewModel(shared *common.SharedModel) Model {
82 hh := headerHeight(shared)
83 ww := headerWidth(shared.Width)
84 viewport := viewport.New(ww, shared.Height-hh)
85 if shared.User != nil {
86 viewport.SetContent(PlusView(shared.User.Name, ww))
87 }
88
89 return Model{
90 shared: shared,
91 viewport: viewport,
92 }
93}
94
95func (m Model) Init() tea.Cmd {
96 return nil
97}
98
99// Update is the Bubble Tea update loop.
100func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
101 switch msg := msg.(type) {
102 case tea.WindowSizeMsg:
103 m.viewport.Width = headerWidth(msg.Width)
104 hh := headerHeight(m.shared)
105 m.viewport.Height = msg.Height - hh
106 case tea.KeyMsg:
107 switch msg.String() {
108 case "q", "esc":
109 return m, pages.Navigate(pages.MenuPage)
110 }
111 }
112
113 var cmd tea.Cmd
114 m.viewport, cmd = m.viewport.Update(msg)
115 return m, cmd
116}
117
118// View renders current view from the model.
119func (m Model) View() string {
120 return m.viewport.View()
121}