/ 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  }