/ client.go
client.go
1 package main 2 3 import ( 4 "encoding/json" 5 "log" 6 "strconv" 7 8 "github.com/maxence-charriere/go-app/v10/pkg/app" 9 shell "github.com/stateless-minds/go-ipfs-api" 10 ) 11 12 // client is a component that holds cyber-gubi. A component is a 13 // customizable, independent, and reusable UI element. It is created by 14 // embedding app.Compo into a struct. 15 type client struct { 16 app.Compo 17 sh *shell.Shell 18 loggedIn bool 19 businessName string 20 wallet Wallet 21 plan Plan 22 subscriptions []Subscription 23 totalIncome int 24 observer app.Value 25 callback app.Func 26 lastIndex int 27 indexStep int 28 } 29 30 func (c *client) OnMount(ctx app.Context) { 31 sh := shell.NewShell("localhost:5001") 32 c.sh = sh 33 c.indexStep = 99 34 35 ctx.GetState("loggedIn", &c.loggedIn) 36 if !c.loggedIn { 37 ctx.Navigate("/auth") 38 } 39 40 c.callback = app.FuncOf(func(this app.Value, args []app.Value) interface{} { 41 entries := args[0] 42 for i := 0; i < entries.Length(); i++ { 43 entry := entries.Index(i) 44 if entry.Get("isIntersecting").Bool() { 45 // Element is visible - do something 46 c.getSubscriptions(ctx) 47 } 48 } 49 return nil 50 }) 51 52 // Select the root element by class name 53 rootElement := app.Window().Get("document").Call("querySelector", ".list") 54 55 options := map[string]interface{}{ 56 "root": rootElement, 57 "rootMargin": "0px", 58 "threshold": 1, 59 } 60 61 observerConstructor := app.Window().Get("IntersectionObserver") 62 c.observer = observerConstructor.New(c.callback, options) 63 64 ctx.GetState("businessName", &c.businessName) 65 66 ctx.GetState("balance", &c.wallet) 67 68 ctx.GetState("plan", &c.plan) 69 70 c.getSubscriptions(ctx) 71 } 72 73 func (c *client) OnUpdate(ctx app.Context) { 74 // Wrap your observation logic in a Go function 75 callback := func() { 76 target := app.Window().GetElementByID("last-item") 77 if !target.IsNull() && !target.IsUndefined() { 78 c.observer.Call("disconnect") 79 c.observer.Call("observe", target) 80 } 81 } 82 83 var goFunc app.Func 84 85 // Wrap callback as JS function 86 goFunc = app.FuncOf(func(this app.Value, args []app.Value) interface{} { 87 callback() 88 goFunc.Release() // release after call to avoid leaks 89 return nil 90 }) 91 92 // Call JS setTimeout with delay 10ms 93 app.Window().Call("goAppSetTimeout", goFunc, 100) 94 } 95 96 func (c *client) OnDismount(ctx app.Context) { 97 c.observer.Call("disconnect") 98 c.callback.Release() 99 } 100 101 func (c *client) getSubscriptions(ctx app.Context) { 102 ctx.Async(func() { 103 rangeStart := strconv.Itoa(c.lastIndex) 104 rangeEnd := strconv.Itoa(c.lastIndex + c.indexStep) 105 subs, err := c.sh.OrbitDocsQuery(dbSubscription, "plan_id", c.plan.ID+",range="+rangeStart+"-"+rangeEnd) 106 if err != nil { 107 log.Fatal(err) 108 } 109 110 subscriptions := []Subscription{} 111 var totalIncome int 112 113 if len(subs) != 0 { 114 err = json.Unmarshal(subs, &subscriptions) // Unmarshal the byte slice directly 115 if err != nil { 116 log.Fatal(err) 117 } 118 119 for _, sub := range subscriptions { 120 totalIncome += sub.Price 121 } 122 } else { 123 c.OnDismount(ctx) 124 } 125 126 ctx.Dispatch(func(ctx app.Context) { 127 c.subscriptions = append(c.subscriptions, subscriptions...) 128 c.totalIncome = totalIncome 129 c.lastIndex = c.lastIndex + 1 + c.indexStep 130 c.OnUpdate(ctx) 131 }) 132 }) 133 } 134 135 // The Render method is where the component appearance is defined. Here, a 136 // client is displayed. 137 func (c *client) Render() app.UI { 138 return app.Div().Class("container").Body( 139 app.Div().Class("mobile").Body( 140 app.Div().Class("header").Body( 141 newNav(), 142 app.Div().Class("header-summary").Body( 143 app.Span().Class("logo").Text("cyber-gubi"), 144 app.Div().Class("summary-text").Body( 145 app.Span().Text("Recurring"), 146 ), 147 app.Div().Class("summary-balance").Body( 148 app.Span().Text(strconv.Itoa(c.totalIncome/100)+" GUBI"), 149 ), 150 ), 151 ), 152 app.Div().ID("content").Body( 153 app.Div().Class("card").Body( 154 app.Div().Class("upper-row single").Body( 155 app.Div().Class("card-item").Body( 156 app.Span().Class("span-header-sub").Text("Clients"), 157 ), 158 ), 159 ), 160 app.Div().Class("list").Body( 161 app.If(len(c.subscriptions) == 0, func() app.UI { 162 return app.Div().Class("list-item").Body( 163 app.Span().Class("empty").Text("No subscriptions yet"), 164 ).Style("pointer-events", "none") 165 }), 166 app.Range(c.subscriptions).Slice(func(i int) app.UI { 167 return app.If(i == len(c.subscriptions)-1 && len(c.subscriptions)%5 == 0, func() app.UI { 168 return app.Div().ID("last-item").Class("list-item").Body( 169 app.Div().Class("s-details").Body( 170 app.Div().Class("c-title").Body( 171 app.Span().Text("User ID: "+c.subscriptions[i].UserID), 172 ), 173 app.Div().Class("s-time").Body( 174 app.Span().Text(c.subscriptions[i].StartDate.Format("2006-01-02 15:04")), 175 app.Span().Text(c.subscriptions[i].EndDate.Format("2006-01-02 15:04")), 176 ), 177 ), 178 app.Div().Class("s-price").Body( 179 app.Span().Text(strconv.Itoa(c.subscriptions[i].Price/100)+" GUBI"), 180 ), 181 ) 182 }).Else(func() app.UI { 183 return app.Div().Class("list-item").Body( 184 app.Div().Class("s-details").Body( 185 app.Div().Class("c-title").Body( 186 app.Span().Text("User ID: "+c.subscriptions[i].UserID), 187 ), 188 app.Div().Class("s-time").Body( 189 app.Span().Text(c.subscriptions[i].StartDate.Format("2006-01-02 15:04")), 190 app.Span().Text(c.subscriptions[i].EndDate.Format("2006-01-02 15:04")), 191 ), 192 ), 193 app.Div().Class("s-price").Body( 194 app.Span().Text(strconv.Itoa(c.subscriptions[i].Price/100)+" GUBI"), 195 ), 196 ) 197 }) 198 }), 199 ), 200 ), 201 ), 202 ) 203 }