/ transactions.go
transactions.go
1 package main 2 3 import ( 4 "encoding/json" 5 "log" 6 "strconv" 7 "strings" 8 9 "github.com/maxence-charriere/go-app/v10/pkg/app" 10 shell "github.com/stateless-minds/go-ipfs-api" 11 ) 12 13 // transactions is a component that holds cyber-gubi. A component is a 14 // customizable, independent, and reusable UI element. It is created by 15 // embedding app.Compo into a struct. 16 type transactions struct { 17 app.Compo 18 sh *shell.Shell 19 loggedIn bool 20 userID string 21 wallet Wallet 22 transactions []Transaction 23 observer app.Value 24 callback app.Func 25 lastIndex int 26 indexStep int 27 } 28 29 func (t *transactions) OnMount(ctx app.Context) { 30 sh := shell.NewShell("localhost:5001") 31 t.sh = sh 32 t.indexStep = 99 33 34 ctx.GetState("loggedIn", &t.loggedIn) 35 if !t.loggedIn { 36 ctx.Navigate("/auth") 37 } 38 39 urlPath := app.Window().URL().Path 40 41 fragments := strings.Split(urlPath, "transactions/") 42 43 if len(fragments) > 1 { 44 t.userID = fragments[1] 45 } 46 47 t.callback = app.FuncOf(func(this app.Value, args []app.Value) interface{} { 48 entries := args[0] 49 for i := 0; i < entries.Length(); i++ { 50 entry := entries.Index(i) 51 if entry.Get("isIntersecting").Bool() { 52 // Element is visible - do something 53 t.getTransactions(ctx) 54 } 55 } 56 return nil 57 }) 58 59 // Select the root element by class name 60 rootElement := app.Window().Get("document").Call("querySelector", ".list") 61 62 options := map[string]interface{}{ 63 "root": rootElement, 64 "rootMargin": "0px", 65 "threshold": 1, 66 } 67 68 observerConstructor := app.Window().Get("IntersectionObserver") 69 t.observer = observerConstructor.New(t.callback, options) 70 71 if t.userID == "" { 72 ctx.GetState("userID", &t.userID) 73 } 74 75 ctx.GetState("balance", &t.wallet) 76 77 t.getTransactions(ctx) 78 } 79 80 func (t *transactions) OnUpdate(ctx app.Context) { 81 // Wrap your observation logic in a Go function 82 callback := func() { 83 target := app.Window().GetElementByID("last-item") 84 if !target.IsNull() && !target.IsUndefined() { 85 t.observer.Call("disconnect") 86 t.observer.Call("observe", target) 87 } 88 } 89 90 var goFunc app.Func 91 92 // Wrap callback as JS function 93 goFunc = app.FuncOf(func(this app.Value, args []app.Value) interface{} { 94 callback() 95 goFunc.Release() // release after call to avoid leaks 96 return nil 97 }) 98 99 // Call JS setTimeout with delay 10ms 100 app.Window().Call("goAppSetTimeout", goFunc, 100) 101 } 102 103 func (t *transactions) OnDismount(ctx app.Context) { 104 t.observer.Call("disconnect") 105 t.callback.Release() 106 } 107 108 func (t *transactions) getTransactions(ctx app.Context) { 109 ctx.Async(func() { 110 rangeStart := strconv.Itoa(t.lastIndex) 111 rangeEnd := strconv.Itoa(t.lastIndex + t.indexStep) 112 // trs, err := t.sh.OrbitDocsQuery(dbTransaction, "all", "") 113 trs, err := t.sh.OrbitDocsQuery(dbTransaction, "sender_id,receiver_id", t.userID+",range="+rangeStart+"-"+rangeEnd) 114 if err != nil { 115 log.Fatal(err) 116 } 117 118 transactions := []Transaction{} 119 120 if len(trs) != 0 { 121 err = json.Unmarshal(trs, &transactions) // Unmarshal the byte slice directly 122 if err != nil { 123 log.Fatal(err) 124 } 125 } else { 126 t.OnDismount(ctx) 127 } 128 129 ctx.Dispatch(func(ctx app.Context) { 130 t.transactions = transactions 131 t.lastIndex = t.lastIndex + 1 + t.indexStep 132 t.OnUpdate(ctx) 133 }) 134 }) 135 } 136 137 func (t *transactions) showTransactionDetails(ctx app.Context, e app.Event) { 138 ctx.JSSrc().Call("setAttribute", "style", "height: auto") 139 } 140 141 func (t *transactions) hideTransactionDetails(ctx app.Context, e app.Event) { 142 ctx.JSSrc().Call("setAttribute", "style", "height: 55px") 143 } 144 145 func (t *transactions) exportTransactions(ctx app.Context, e app.Event) { 146 e.PreventDefault() 147 trs, err := json.Marshal(t.transactions) 148 if err != nil { 149 log.Fatal(err) 150 } 151 152 blob := app.Window().Get("Blob").New( 153 []interface{}{string(trs)}, 154 map[string]interface{}{"type": "application/json"}, 155 ) 156 157 // Create Object URL 158 url := app.Window().Get("URL").Call("createObjectURL", blob) 159 160 // Create a temporary <a> element 161 a := app.Window().Get("document").Call("createElement", "a") 162 a.Set("href", url) 163 a.Set("download", "transactions.json") 164 165 // Append to body, click to trigger download, then remove 166 app.Window().Get("document").Get("body").Call("appendChild", a) 167 a.Call("click") 168 a.Get("parentNode").Call("removeChild", a) 169 170 // Revoke the object URL to avoid memory leaks 171 app.Window().Get("URL").Call("revokeObjectURL", url) 172 } 173 174 // The Render method is where the component appearance is defined. Here, a 175 // payment form is displayed. 176 func (t *transactions) Render() app.UI { 177 return app.Div().Class("container").Body( 178 app.Div().Class("mobile").Body( 179 app.Div().Class("header").Body( 180 newNav(), 181 app.Div().Class("header-summary").Body( 182 app.Span().Class("logo").Text("cyber-gubi"), 183 app.Div().Class("summary-text").Body( 184 app.Span().Text("Balance"), 185 ), 186 app.Div().Class("summary-balance").Body( 187 app.Span().Text(strconv.Itoa(t.wallet.Balance/100)+" GUBI"), 188 ), 189 ), 190 ), 191 app.Div().ID("content").Body( 192 app.Div().Class("card").Body( 193 app.Div().Class("upper-row single").Body( 194 app.Div().Class("card-item").Body( 195 app.Span().Class("span-header-sub").Text("Transactions"), 196 ), 197 ), 198 ), 199 app.Div().Class("list transactions").Body( 200 app.If(len(t.transactions) == 0, func() app.UI { 201 return app.Div().Class("list-item").Body( 202 app.Span().Class("empty").Text("No transactions found"), 203 ).Style("pointer-events", "none") 204 }), 205 app.Range(t.transactions).Slice(func(i int) app.UI { 206 return app.If(i == len(t.transactions)-1 && len(t.transactions)%5 == 0, func() app.UI { 207 return app.Div().ID("last-item").Class("list-item").Body( 208 app.Div().Class("t-details").Body( 209 app.Div().Class("t-title").Body( 210 app.If(t.transactions[i].SenderID == t.userID, func() app.UI { 211 return app.Span().Text("Purchase ID: " + t.transactions[i].ID) 212 }).Else(func() app.UI { 213 return app.Span().Text("Sale ID: " + t.transactions[i].ID) 214 }), 215 ), 216 app.Div().Class("t-time").Body( 217 app.Span().Text(t.transactions[i].Timestamp.Format("2006-01-02 15:04:05")), 218 ), 219 app.Div().Class("t-more-details").Body( 220 app.Div().Class("col-1").Body( 221 app.Span().Text("Item"), 222 app.Range(t.transactions[i].ProductsServices).Slice(func(n int) app.UI { 223 return app.Span().Text(t.transactions[i].ProductsServices[n].Name) 224 }), 225 ), 226 app.Div().Class("col-2").Body( 227 app.Span().Text("Amount"), 228 app.Range(t.transactions[i].ProductsServices).Slice(func(n int) app.UI { 229 return app.Span().Text(t.transactions[i].ProductsServices[n].Amount) 230 }), 231 ), 232 app.Div().Class("col-3").Body( 233 app.Span().Text("Price"), 234 app.Range(t.transactions[i].ProductsServices).Slice(func(n int) app.UI { 235 return app.Span().Text(t.transactions[i].ProductsServices[n].Price / 100) 236 }), 237 ), 238 ), 239 ).OnMouseOver(t.showTransactionDetails).OnMouseLeave(t.hideTransactionDetails), 240 app.Div().Class("t-price").Body( 241 app.If(t.transactions[i].SenderID == t.userID, func() app.UI { 242 return app.Span().Text("-" + strconv.Itoa(t.transactions[i].TotalCost/100) + " GUBI") 243 }).Else(func() app.UI { 244 return app.Span().Text("+" + strconv.Itoa(t.transactions[i].TotalCost/100) + " GUBI") 245 }), 246 ), 247 ) 248 }).Else(func() app.UI { 249 return app.Div().Class("list-item").Body( 250 app.Div().Class("t-details").Body( 251 app.Div().Class("t-title").Body( 252 app.If(t.transactions[i].SenderID == t.userID, func() app.UI { 253 return app.Span().Text("Purchase ID: " + t.transactions[i].ID) 254 }).Else(func() app.UI { 255 return app.Span().Text("Sale ID: " + t.transactions[i].ID) 256 }), 257 ), 258 app.Div().Class("t-time").Body( 259 app.Span().Text(t.transactions[i].Timestamp.Format("2006-01-02 15:04:05")), 260 ), 261 app.Div().Class("t-more-details").Body( 262 app.Div().Class("col-1").Body( 263 app.Span().Text("Item"), 264 app.Range(t.transactions[i].ProductsServices).Slice(func(n int) app.UI { 265 return app.Span().Text(t.transactions[i].ProductsServices[n].Name) 266 }), 267 ), 268 app.Div().Class("col-2").Body( 269 app.Span().Text("Amount"), 270 app.Range(t.transactions[i].ProductsServices).Slice(func(n int) app.UI { 271 return app.Span().Text(t.transactions[i].ProductsServices[n].Amount) 272 }), 273 ), 274 app.Div().Class("col-3").Body( 275 app.Span().Text("Price"), 276 app.Range(t.transactions[i].ProductsServices).Slice(func(n int) app.UI { 277 return app.Span().Text(t.transactions[i].ProductsServices[n].Price / 100) 278 }), 279 ), 280 ), 281 ).OnMouseOver(t.showTransactionDetails).OnMouseLeave(t.hideTransactionDetails), 282 app.Div().Class("t-price").Body( 283 app.If(t.transactions[i].SenderID == t.userID, func() app.UI { 284 return app.Span().Text("-" + strconv.Itoa(t.transactions[i].TotalCost/100) + " GUBI") 285 }).Else(func() app.UI { 286 return app.Span().Text("+" + strconv.Itoa(t.transactions[i].TotalCost/100) + " GUBI") 287 }), 288 ), 289 ) 290 }) 291 }), 292 ), 293 app.If(len(t.transactions) > 0, func() app.UI { 294 return app.Div().Class("menu-btn").Body( 295 app.A().Class("submit submit-list").Type("submit").Text("Export").OnClick(t.exportTransactions), 296 ) 297 }), 298 ), 299 ), 300 ) 301 }