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