/ collect-tax.go
collect-tax.go
  1  package main
  2  
  3  import (
  4  	"encoding/json"
  5  	"log"
  6  	"strconv"
  7  	"strings"
  8  	"time"
  9  
 10  	"github.com/google/uuid"
 11  	"github.com/maxence-charriere/go-app/v10/pkg/app"
 12  	shell "github.com/stateless-minds/go-ipfs-api"
 13  )
 14  
 15  // payment is a component that holds cyber-gubi. A component is a
 16  // customizable, independent, and reusable UI element. It is created by
 17  // embedding app.Compo into a struct.
 18  type tax struct {
 19  	app.Compo
 20  	sh        *shell.Shell
 21  	loggedIn  bool
 22  	userID    string
 23  	taxUserID string
 24  	taxAmount int
 25  	wallet    Wallet
 26  	taxWallet Wallet
 27  }
 28  
 29  func (t *tax) OnMount(ctx app.Context) {
 30  	sh := shell.NewShell("localhost:5001")
 31  	t.sh = sh
 32  
 33  	ctx.GetState("loggedIn", &t.loggedIn)
 34  	if !t.loggedIn {
 35  		ctx.Navigate("/auth")
 36  	}
 37  
 38  	urlPath := app.Window().URL().Path
 39  
 40  	fragments := strings.Split(urlPath, "tax/")
 41  
 42  	if len(fragments) > 1 {
 43  		t.taxUserID = fragments[1]
 44  	}
 45  
 46  	if t.taxUserID == "" {
 47  		ctx.Navigate("/auth")
 48  	}
 49  
 50  	ctx.GetState("userID", &t.userID)
 51  
 52  	ctx.GetState("balance", &t.wallet)
 53  
 54  	taxWallet, err := t.getBalance(t.taxUserID)
 55  	if err != nil {
 56  		log.Fatal("wallet not found")
 57  	}
 58  
 59  	t.taxWallet = taxWallet
 60  }
 61  
 62  func (t *tax) getBalance(userID string) (balance Wallet, err error) {
 63  	b, err := t.sh.OrbitDocsQuery(dbWallet, "_id", userID)
 64  	if err != nil {
 65  		return Wallet{}, err
 66  	}
 67  
 68  	if len(b) == 0 {
 69  		return Wallet{}, err
 70  	}
 71  
 72  	wallets := []Wallet{}
 73  
 74  	err = json.Unmarshal(b, &wallets) // Unmarshal the byte slice directly
 75  	if err != nil {
 76  		return Wallet{}, err
 77  	}
 78  
 79  	return wallets[0], nil
 80  }
 81  
 82  func (t *tax) updateBalance(userID string, balance, income int, date string) error {
 83  	wallet := Wallet{
 84  		ID:           userID,
 85  		Balance:      balance,
 86  		Income:       income,
 87  		LastReceived: date,
 88  	}
 89  
 90  	walletJSON, err := json.Marshal(wallet)
 91  	if err != nil {
 92  		return err
 93  	}
 94  
 95  	err = t.sh.OrbitDocsPut(dbWallet, walletJSON)
 96  	if err != nil {
 97  		return err
 98  	}
 99  
100  	return nil
101  }
102  
103  func (t *tax) storeTransaction(transaction Transaction) error {
104  	transactionJSON, err := json.Marshal(transaction)
105  	if err != nil {
106  		return err
107  	}
108  
109  	err = t.sh.OrbitDocsPut(dbTransaction, transactionJSON)
110  	if err != nil {
111  		return err
112  	}
113  
114  	return nil
115  }
116  
117  func (t *tax) withdrawTax(ctx app.Context, e app.Event) {
118  	e.PreventDefault()
119  
120  	valid := app.Window().GetElementByID("pay-form").Call("reportValidity").Bool()
121  	if valid {
122  		transaction := Transaction{}
123  		transaction.ID = uuid.NewString()
124  		transaction.SenderID = t.taxUserID
125  		transaction.ReceiverID = t.userID
126  		transaction.TotalCost = t.taxAmount * 100
127  		transaction.Timestamp = time.Now()
128  		transaction.Date = strconv.Itoa(time.Now().Year()) + "/" + strconv.Itoa(int(time.Now().Month()))
129  
130  		if t.taxWallet.Balance-transaction.TotalCost < 0 {
131  			ctx.Notifications().New(app.Notification{
132  				Title: "Error",
133  				Body:  "Not enough funds.",
134  			})
135  			return
136  		}
137  		// update taxable balance
138  		err := t.updateBalance(t.taxUserID, t.taxWallet.Balance-transaction.TotalCost, t.taxWallet.Income, t.taxWallet.LastReceived)
139  		if err != nil {
140  			log.Fatal(err)
141  		}
142  		// get treasury balance
143  		receiverBalance, err := t.getBalance(t.userID)
144  		if err != nil {
145  			log.Fatal(err)
146  		}
147  		// update receiver balance
148  		err = t.updateBalance(transaction.ReceiverID, receiverBalance.Balance+transaction.TotalCost, receiverBalance.Income, receiverBalance.LastReceived)
149  		if err != nil {
150  			// rollback sender balance
151  			err := t.updateBalance(t.taxUserID, t.taxWallet.Balance+transaction.TotalCost, t.taxWallet.Income, t.taxWallet.LastReceived)
152  			if err != nil {
153  				log.Fatal(err)
154  			}
155  			return
156  		}
157  		// store transaction
158  		err = t.storeTransaction(transaction)
159  		if err != nil {
160  			// rollback sender balance
161  			err = t.updateBalance(t.taxUserID, t.taxWallet.Balance+transaction.TotalCost, t.taxWallet.Income, t.taxWallet.LastReceived)
162  			if err != nil {
163  				log.Fatal(err)
164  			}
165  			// rollback receiver balance
166  			err = t.updateBalance(transaction.ReceiverID, receiverBalance.Balance-transaction.TotalCost, receiverBalance.Income, receiverBalance.LastReceived)
167  			if err != nil {
168  				log.Fatal(err)
169  			}
170  			return
171  		}
172  
173  		t.wallet.Balance = t.wallet.Balance + transaction.TotalCost
174  		ctx.Update()
175  
176  		ctx.Notifications().New(app.Notification{
177  			Title: "Success",
178  			Body:  "Payment successful!",
179  		})
180  	}
181  }
182  
183  // The Render method is where the component appearance is defined. Here, a
184  // payment form is displayed.
185  func (t *tax) Render() app.UI {
186  	return app.Div().Class("container").Body(
187  		app.Div().Class("mobile").Body(
188  			app.Div().Class("header").Body(
189  				newNav(),
190  				app.Div().Class("header-summary").Body(
191  					app.Span().Class("logo").Text("cyber-gubi"),
192  					app.Div().Class("summary-text").Body(
193  						app.Span().Text("Treasury"),
194  					),
195  					app.Div().Class("summary-balance").Body(
196  						app.Span().Text(strconv.Itoa(t.wallet.Balance/100)+" GUBI"),
197  					),
198  				),
199  			),
200  			app.Div().ID("content").Body(
201  				app.Div().Class("card").Body(
202  					app.Div().Class("upper-row").Body(
203  						app.Div().Class("card-item").Body(
204  							app.Span().Class("span-header").Text("Collect Tax"),
205  							app.Form().ID("pay-form").Body(
206  								app.Label().Class("tax-label").Text("Balance"),
207  								app.Input().Type("number").Value(t.taxWallet.Balance/100).Disabled(true),
208  								app.Input().Type("number").Placeholder("Amount to collect").Required(true).OnChange(t.ValueTo(&t.taxAmount)),
209  								app.Div().Class("drawer drawer-pay").Body(
210  									app.Div().Class("menu-btn").Body(
211  										app.Button().Class("submit").Type("submit").Text("Withdraw").OnClick(t.withdrawTax),
212  									),
213  								),
214  							),
215  						),
216  					),
217  				),
218  			),
219  		),
220  	)
221  }