/ wallets.go
wallets.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 // wallets 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 wallets struct { 16 app.Compo 17 sh *shell.Shell 18 loggedIn bool 19 userID string 20 countryCode string 21 wallet Wallet 22 wallets []Wallet 23 observer app.Value 24 callback app.Func 25 lastIndex int 26 indexStep int 27 } 28 29 func (w *wallets) OnMount(ctx app.Context) { 30 sh := shell.NewShell("localhost:5001") 31 w.sh = sh 32 w.indexStep = 99 33 34 ctx.GetState("loggedIn", &w.loggedIn) 35 if !w.loggedIn { 36 ctx.Navigate("/auth") 37 } 38 39 w.callback = app.FuncOf(func(this app.Value, args []app.Value) interface{} { 40 entries := args[0] 41 for i := 0; i < entries.Length(); i++ { 42 entry := entries.Index(i) 43 if entry.Get("isIntersecting").Bool() { 44 // Element is visible - do something 45 w.getWallets(ctx) 46 } 47 } 48 return nil 49 }) 50 51 // Select the root element by class name 52 rootElement := app.Window().Get("document").Call("querySelector", ".list") 53 54 options := map[string]interface{}{ 55 "root": rootElement, 56 "rootMargin": "0px", 57 "threshold": 1, 58 } 59 60 observerConstructor := app.Window().Get("IntersectionObserver") 61 w.observer = observerConstructor.New(w.callback, options) 62 63 ctx.GetState("userID", &w.userID) 64 ctx.GetState("balance", &w.wallet) 65 66 ctx.GetState("countryCode", &w.countryCode) 67 68 w.getWallets(ctx) 69 } 70 71 func (w *wallets) OnUpdate(ctx app.Context) { 72 // Wrap your observation logic in a Go function 73 callback := func() { 74 target := app.Window().GetElementByID("last-item") 75 if !target.IsNull() && !target.IsUndefined() { 76 w.observer.Call("disconnect") 77 w.observer.Call("observe", target) 78 } 79 } 80 81 var goFunc app.Func 82 83 // Wrap callback as JS function 84 goFunc = app.FuncOf(func(this app.Value, args []app.Value) interface{} { 85 callback() 86 goFunc.Release() // release after call to avoid leaks 87 return nil 88 }) 89 90 // Call JS setTimeout with delay 10ms 91 app.Window().Call("goAppSetTimeout", goFunc, 100) 92 } 93 94 func (w *wallets) OnDismount(ctx app.Context) { 95 w.observer.Call("disconnect") 96 w.callback.Release() 97 } 98 99 func (w *wallets) getWallets(ctx app.Context) { 100 ctx.Async(func() { 101 rangeStart := strconv.Itoa(w.lastIndex) 102 rangeEnd := strconv.Itoa(w.lastIndex + w.indexStep) 103 // wls, err := w.sh.OrbitDocsQuery(dbWallet, "country_code", w.countryCode+",range="+rangeStart+"-"+rangeEnd) 104 wls, err := w.sh.OrbitDocsQuery(dbWallet, "country_code", "BG"+",range="+rangeStart+"-"+rangeEnd) 105 if err != nil { 106 log.Fatal(err) 107 } 108 109 wallets := []Wallet{} 110 111 if len(wls) != 0 { 112 err = json.Unmarshal(wls, &wallets) // Unmarshal the byte slice directly 113 if err != nil { 114 log.Fatal(err) 115 } 116 } else { 117 w.OnDismount(ctx) 118 } 119 120 excludingOwnWallet := []Wallet{} 121 122 for _, wallet := range wallets { 123 if wallet.ID != string(w.userID) { 124 excludingOwnWallet = append(excludingOwnWallet, wallet) 125 } 126 } 127 128 ctx.Dispatch(func(ctx app.Context) { 129 w.wallets = append(w.wallets, excludingOwnWallet...) 130 w.lastIndex = w.lastIndex + 1 + w.indexStep 131 w.OnUpdate(ctx) 132 }) 133 }) 134 } 135 136 func (w *wallets) getBalance(userID string) (balance Wallet, err error) { 137 b, err := w.sh.OrbitDocsQuery(dbWallet, "_id", userID) 138 if err != nil { 139 return Wallet{}, err 140 } 141 142 if len(b) == 0 { 143 return Wallet{}, err 144 } 145 146 wallets := []Wallet{} 147 148 err = json.Unmarshal(b, &wallets) // Unmarshal the byte slice directly 149 if err != nil { 150 return Wallet{}, err 151 } 152 153 return wallets[0], nil 154 } 155 156 func (w *wallets) updateBalance(userID string, balance, income int, date string) error { 157 wallet := Wallet{ 158 ID: userID, 159 Balance: balance, 160 Income: income, 161 LastReceived: date, 162 } 163 164 walletJSON, err := json.Marshal(wallet) 165 if err != nil { 166 return err 167 } 168 169 err = w.sh.OrbitDocsPut(dbWallet, walletJSON) 170 if err != nil { 171 return err 172 } 173 174 return nil 175 } 176 177 func (w *wallets) storeTransaction(transaction Transaction) error { 178 transactionJSON, err := json.Marshal(transaction) 179 if err != nil { 180 return err 181 } 182 183 err = w.sh.OrbitDocsPut(dbTransaction, transactionJSON) 184 if err != nil { 185 return err 186 } 187 188 return nil 189 } 190 191 // The Render method is where the component appearance is defined. Here, a 192 // payment form is displayed. 193 func (w *wallets) Render() app.UI { 194 return app.Div().Class("container").Body( 195 app.Div().Class("mobile").Body( 196 app.Div().Class("header").Body( 197 newNav(), 198 app.Div().Class("header-summary").Body( 199 app.Span().Class("logo").Text("cyber-gubi"), 200 app.Div().Class("summary-text").Body( 201 app.Span().Text("Treasury"), 202 ), 203 app.Div().Class("summary-balance").Body( 204 app.Span().Text(strconv.Itoa(w.wallet.Balance/100)+" GUBI"), 205 ), 206 ), 207 ), 208 app.Div().ID("content").Body( 209 app.Div().Class("card").Body( 210 app.Div().Class("upper-row single").Body( 211 app.Div().Class("card-item").Body( 212 app.Span().Class("span-header-sub").Text("Wallets"), 213 ), 214 ), 215 ), 216 app.Div().Class("list").Body( 217 app.If(len(w.wallets) == 0, func() app.UI { 218 return app.Div().Class("list-item").Body( 219 app.Span().Class("empty").Text("No wallets found"), 220 ).Style("pointer-events", "none") 221 }), 222 app.Range(w.wallets).Slice(func(i int) app.UI { 223 return app.If(i == len(w.wallets)-1 && len(w.wallets)%5 == 0, func() app.UI { 224 return app.Div().ID("last-item").Class("list-item").Body( 225 app.Div().Class("w-details").Body( 226 app.Div().Class("w-title").Body( 227 app.Span().Text(w.wallets[i].ID), 228 ), 229 app.Div().Class("menu-btn menu-list").Body( 230 app.A().Href("transactions").Class("submit submit-list").Type("submit").Text("Transactions"), 231 ), 232 app.Div().Class("menu-btn menu-list").Body( 233 app.A().Href("tax/"+w.wallets[i].ID).Class("submit submit-list").Type("submit").Text("Collect Tax"), 234 ), 235 ), 236 ) 237 }).Else(func() app.UI { 238 return app.Div().Class("list-item").Body( 239 app.Div().Class("w-details").Body( 240 app.Div().Class("w-title").Body( 241 app.Span().Text(w.wallets[i].ID), 242 ), 243 app.Div().Class("menu-btn menu-list").Body( 244 app.A().Href("transactions/"+w.wallets[i].ID).Class("submit submit-list").Type("submit").Text("Transactions"), 245 ), 246 app.Div().Class("menu-btn menu-list").Body( 247 app.A().Href("tax/"+w.wallets[i].ID).Class("submit submit-list").Type("submit").Text("Collect Tax"), 248 ), 249 ), 250 ) 251 }) 252 }), 253 ), 254 ), 255 ), 256 ) 257 }