/ wallet.go
wallet.go
  1  package main
  2  
  3  import (
  4  	"encoding/json"
  5  	"log"
  6  	"sort"
  7  	"strconv"
  8  	"time"
  9  
 10  	"github.com/maxence-charriere/go-app/v10/pkg/app"
 11  	shell "github.com/stateless-minds/go-ipfs-api"
 12  )
 13  
 14  const dbIncome = "income"
 15  const dbWallet = "wallet"
 16  
 17  // wallet is a component that holds cyber-gubi. A component is a
 18  // customizable, independent, and reusable UI element. It is created by
 19  // embedding app.Compo into a struct.
 20  type wallet struct {
 21  	app.Compo
 22  	sh           *shell.Shell
 23  	loggedIn     bool
 24  	isBusiness   bool
 25  	isGovernment bool
 26  	businessName string
 27  	countryCode  string
 28  	countryName  string
 29  	userID       string
 30  	wallet       Wallet
 31  	income       Income
 32  	transactions []Transaction
 33  }
 34  
 35  type Wallet struct {
 36  	ID           string `mapstructure:"_id" json:"_id" validate:"uuid_rfc4122"`                     // Unique identifier for the user
 37  	CountryCode  string `mapstructure:"country_code" json:"country_code" validate:"uuid_rfc4122"`   // Unique identifier for the country
 38  	Balance      int    `mapstructure:"balance" json:"balance" validate:"uuid_rfc4122"`             // Balance of the user in cents
 39  	Income       int    `mapstructure:"income" json:"income" validate:"uuid_rfc4122"`               // Recurring income of the user in cents
 40  	LastReceived string `mapstructure:"last_received" json:"last_received" validate:"uuid_rfc4122"` // Date when basic income was last received
 41  }
 42  
 43  type Income struct {
 44  	ID     string `mapstructure:"_id" json:"_id" validate:"uuid_rfc4122"`       // Unique identifier for the income
 45  	Amount int    `mapstructure:"amount" json:"amount" validate:"uuid_rfc4122"` // Amount of the income in cents
 46  	Period string `mapstructure:"period" json:"period" validate:"uuid_rfc4122"` // Period the income is valid for
 47  }
 48  
 49  func (w *wallet) OnMount(ctx app.Context) {
 50  	sh := shell.NewShell("localhost:5001")
 51  	w.sh = sh
 52  
 53  	ctx.GetState("loggedIn", &w.loggedIn)
 54  	if !w.loggedIn {
 55  		ctx.Navigate("/auth")
 56  	}
 57  
 58  	ctx.GetState("userID", &w.userID)
 59  
 60  	ctx.GetState("isBusiness", &w.isBusiness)
 61  
 62  	ctx.GetState("businessName", &w.businessName)
 63  
 64  	ctx.GetState("isGovernment", &w.isGovernment)
 65  
 66  	ctx.GetState("countryCode", &w.countryCode)
 67  
 68  	ctx.GetState("countryName", &w.countryName)
 69  
 70  	w.getBalance(ctx)
 71  }
 72  
 73  func (w *wallet) getTransactions(ctx app.Context) {
 74  	ctx.Async(func() {
 75  		t, err := w.sh.OrbitDocsQuery(dbTransaction, "sender_id,receiver_id", w.userID)
 76  		if err != nil {
 77  			log.Fatal(err)
 78  		}
 79  
 80  		transactions := []Transaction{}
 81  
 82  		if len(t) != 0 {
 83  			err = json.Unmarshal(t, &transactions) // Unmarshal the byte slice directly
 84  			if err != nil {
 85  				log.Fatal(err)
 86  			}
 87  		}
 88  
 89  		ctx.Dispatch(func(ctx app.Context) {
 90  			if len(transactions) > 0 {
 91  				sort.Slice(transactions, func(i, j int) bool {
 92  					return transactions[i].Timestamp.After(transactions[j].Timestamp)
 93  				})
 94  
 95  				w.transactions = append(w.transactions, transactions...)
 96  			}
 97  		})
 98  	})
 99  }
100  
101  func (w *wallet) getOwnPlan(ctx app.Context) {
102  	ctx.Async(func() {
103  		p, err := w.sh.OrbitDocsQuery(dbPlan, "created_by", w.userID)
104  		if err != nil {
105  			log.Fatal(err)
106  		}
107  
108  		plans := []Plan{}
109  
110  		if len(p) != 0 {
111  			err = json.Unmarshal(p, &plans) // Unmarshal the byte slice directly
112  			if err != nil {
113  				log.Fatal(err)
114  			}
115  		}
116  
117  		ctx.Dispatch(func(ctx app.Context) {
118  			if len(p) > 0 {
119  				ctx.SetState("plan", plans[0])
120  			} else {
121  				ctx.SetState("plan", Plan{})
122  			}
123  
124  			w.getTransactions(ctx)
125  		})
126  	})
127  }
128  
129  func (w *wallet) getBalance(ctx app.Context) {
130  	ctx.Async(func() {
131  		b, err := w.sh.OrbitDocsQuery(dbWallet, "_id", w.userID)
132  		if err != nil {
133  			log.Fatal(err)
134  		}
135  
136  		wallets := []Wallet{}
137  
138  		if len(b) == 0 {
139  			ctx.Dispatch(func(ctx app.Context) {
140  				w.wallet = Wallet{}
141  				ctx.SetState("balance", w.wallet)
142  				if !w.isBusiness && !w.isGovernment {
143  					w.getIncome(ctx)
144  					return
145  				} else {
146  					w.updateBalance(ctx)
147  				}
148  			})
149  			return
150  		} else {
151  			err = json.Unmarshal(b, &wallets) // Unmarshal the byte slice directly
152  			if err != nil {
153  				log.Fatal(err)
154  			}
155  		}
156  
157  		ctx.Dispatch(func(ctx app.Context) {
158  			w.wallet = wallets[0]
159  			ctx.SetState("balance", w.wallet)
160  
161  			// check if recurring income was received for this month
162  			if !w.isBusiness && !w.isGovernment && w.wallet.LastReceived != strconv.Itoa(time.Now().Year())+"/"+strconv.Itoa(int(time.Now().Month())) {
163  				w.getIncome(ctx)
164  			} else {
165  				if w.isBusiness {
166  					w.getOwnPlan(ctx)
167  				} else {
168  					w.getTransactions(ctx)
169  				}
170  			}
171  		})
172  	})
173  }
174  
175  func (w *wallet) updateBalance(ctx app.Context) {
176  	ctx.Async(func() {
177  		wallet := Wallet{
178  			ID:           string(w.userID),
179  			Balance:      w.wallet.Balance,
180  			Income:       w.income.Amount,
181  			LastReceived: w.wallet.LastReceived,
182  			CountryCode:  w.countryCode,
183  		}
184  
185  		walletJSON, err := json.Marshal(wallet)
186  		if err != nil {
187  			log.Fatal(err)
188  		}
189  
190  		err = w.sh.OrbitDocsPut(dbWallet, walletJSON)
191  		if err != nil {
192  			log.Fatal(err)
193  		}
194  
195  		ctx.Dispatch(func(ctx app.Context) {
196  			w.getTransactions(ctx)
197  		})
198  	})
199  }
200  
201  func (w *wallet) getIncome(ctx app.Context) {
202  	ctx.Async(func() {
203  		i, err := w.sh.OrbitDocsQuery(dbIncome, "all", "")
204  		if err != nil {
205  			log.Fatal(err)
206  		}
207  
208  		income := []Income{}
209  
210  		if len(i) == 0 {
211  			log.Printf("no income set")
212  		} else {
213  			err = json.Unmarshal([]byte(i), &income) // Unmarshal the byte slice directly
214  			if err != nil {
215  				log.Fatal(err)
216  			}
217  
218  			ctx.Dispatch(func(ctx app.Context) {
219  				for _, inc := range income {
220  					if inc.Period == strconv.Itoa(time.Now().Year())+"/"+strconv.Itoa(int(time.Now().Month())) {
221  						w.income = inc
222  					}
223  				}
224  
225  				// check if there is a matching income year and month to current moment
226  				if w.income.Period == strconv.Itoa(time.Now().Year())+"/"+strconv.Itoa(int(time.Now().Month())) {
227  					w.wallet.Balance = (w.wallet.Balance + w.income.Amount)
228  					w.wallet.Income = w.income.Amount
229  					w.wallet.LastReceived = strconv.Itoa(time.Now().Year()) + "/" + strconv.Itoa(int(time.Now().Month()))
230  					ctx.SetState("balance", w.wallet)
231  					w.updateBalance(ctx)
232  				} else {
233  					w.getTransactions(ctx)
234  				}
235  			})
236  		}
237  	})
238  }
239  
240  func (w *wallet) goToPayments(ctx app.Context, e app.Event) {
241  	ctx.Navigate("payment")
242  }
243  
244  // The Render method is where the component appearance is defined. Here, a
245  // wallet is displayed.
246  func (w *wallet) Render() app.UI {
247  	return app.Div().Class("container").Body(
248  		app.Div().Class("mobile").Body(
249  			app.Div().Class("header").Body(
250  				newNav(),
251  				app.Div().Class("header-summary").Body(
252  					app.Span().Class("logo").Text("cyber-gubi"),
253  					app.Div().Class("summary-text").Body(
254  						app.Span().Text("Balance"),
255  					),
256  					app.Div().Class("summary-balance").Body(
257  						app.Span().Text(strconv.Itoa(w.wallet.Balance/100)+" GUBI"),
258  					),
259  				),
260  			),
261  			app.Div().ID("content").Body(
262  				app.Div().Class("card card-wallet").Body(
263  					app.Div().Class("upper-row").Body(
264  						app.If(w.isBusiness, func() app.UI {
265  							return app.Div().Class("card-item").Body(
266  								app.Span().Class("span-header").Text("Business Name"),
267  								app.Span().Class("span-body").Text(w.businessName),
268  							)
269  						}).ElseIf(w.isGovernment, func() app.UI {
270  							return app.Div().Class("card-item").Body(
271  								app.Span().Class("span-header").Text("Country Name"),
272  								app.Span().Class("span-body").Text(w.countryName),
273  							)
274  						}).Else(func() app.UI {
275  							return app.Div().Class("card-item").Body(
276  								app.Span().Class("span-header").Text("Monthly Recurring"),
277  								app.Span().Text(strconv.Itoa(w.wallet.Income/100)+" GUBI"),
278  							)
279  						}),
280  					),
281  					app.Div().Class("lower-row").Body(
282  						app.Div().Class("card-item").Body(
283  							app.Span().Class("span-header").Text("Payment ID"),
284  							app.Span().Class("span-body").Text(w.userID),
285  						),
286  					),
287  				),
288  				app.Div().Class("menu-btn").Body(
289  					app.Button().Class("submit").Type("submit").Text("Make a payment").OnClick(w.goToPayments),
290  				),
291  			),
292  		),
293  	)
294  }