From c9ec6c9f6cbe75596972f39d5c516e7a7cce5335 Mon Sep 17 00:00:00 2001 From: meskio <meskio@sindominio.net> Date: Fri, 9 Apr 2021 19:14:59 +0200 Subject: [PATCH] Implement notifications email/desktop * Closes: #24 --- api/api.go | 3 + api/auth.go | 1 + api/db/db.go | 6 +- api/db/db_test.go | 35 ++++++++ api/db/notifications.go | 87 +++++++++++++++++++ api/db/notifications_test.go | 113 +++++++++++++++++++++++++ api/db/preferences.go | 59 +++++++++++++ api/db/preferences_test.go | 53 ++++++++++++ api/mail.go | 47 ++++++++++ api/member.go | 37 ++++++++ api/member_test.go | 76 +++++++++++++++++ api/order.go | 8 ++ src/App.js | 10 ++- src/Head.js | 5 +- src/Panel.js | 12 ++- src/notifications.js | 41 ++++++++- src/preferences/Notifications.js | 71 ++++++++++++++++ src/{ => preferences}/OwnPassword.js | 2 +- src/{ => preferences}/PasswordForm.js | 0 src/{ => preferences}/ResetPassword.js | 4 +- src/{ => preferences}/ResetRequest.js | 2 +- 21 files changed, 653 insertions(+), 19 deletions(-) create mode 100644 api/db/db_test.go create mode 100644 api/db/notifications.go create mode 100644 api/db/notifications_test.go create mode 100644 api/db/preferences.go create mode 100644 api/db/preferences_test.go create mode 100644 src/preferences/Notifications.js rename src/{ => preferences}/OwnPassword.js (97%) rename src/{ => preferences}/PasswordForm.js (100%) rename src/{ => preferences}/ResetPassword.js (97%) rename src/{ => preferences}/ResetRequest.js (97%) diff --git a/api/api.go b/api/api.go index ae38e00..9e62f1f 100644 --- a/api/api.go +++ b/api/api.go @@ -44,6 +44,9 @@ func Init(dbPath string, signKey string, mail *Mail, r *mux.Router) error { r.HandleFunc("/member/{num:[0-9]+}/transactions", a.authAdmin(a.GetMemberTransactions)).Methods("GET") r.HandleFunc("/member/{num:[0-9]+}/purchase", a.authAdminNum(a.AddMemberPurchase)).Methods("POST") + r.HandleFunc("/preferences", a.authNum(a.GetPreferences)).Methods("GET") + r.HandleFunc("/preferences", a.authNum(a.SetPreferences)).Methods("PUT") + r.HandleFunc("/product", a.auth(a.ListProducts)).Methods("GET") r.HandleFunc("/product", a.authAdmin(a.AddProduct)).Methods("POST") r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.GetProduct)).Methods("GET") diff --git a/api/auth.go b/api/auth.go index 5937107..7027ffa 100644 --- a/api/auth.go +++ b/api/auth.go @@ -95,6 +95,7 @@ func (a *api) GetUpdates(w http.ResponseWriter, req *http.Request) { data := make(map[string]interface{}) data["role"] = member.Role + data["notifications"] = a.db.GetNotifications(num) data["token"] = token // FIXME: just for backward compatibility, remove soon if member.Role != roleClaim || tokenNeedsRenew(claims) { diff --git a/api/db/db.go b/api/db/db.go index d7752f5..2aef2af 100644 --- a/api/db/db.go +++ b/api/db/db.go @@ -15,8 +15,8 @@ func Init(dbPath string) (*DB, error) { return nil, err } - db.AutoMigrate(&Member{}, &Product{}, &Purchase{}, &Topup{}, &Transaction{}, - &OrderPurchase{}, &Order{}, &PasswordReset{}, &Supplier{}, - &Inventary{}, &InventaryProduct{}) + db.AutoMigrate(&Member{}, &Preferences{}, &Product{}, &Purchase{}, &Topup{}, + &Transaction{}, &OrderPurchase{}, &Order{}, &PasswordReset{}, + &Supplier{}, &Inventary{}, &InventaryProduct{}, &Notification{}) return &DB{db}, err } diff --git a/api/db/db_test.go b/api/db/db_test.go new file mode 100644 index 0000000..8fdb909 --- /dev/null +++ b/api/db/db_test.go @@ -0,0 +1,35 @@ +package db + +import ( + "io/ioutil" + "os" + "path" +) + +var member = Member{ + Num: 10, + Email: "foo@example.com", +} + +func initTests() (*DB, func(), error) { + testPath, err := ioutil.TempDir(os.TempDir(), "cicer-test-") + if err != nil { + return nil, nil, err + } + + closeFn := func() { + os.RemoveAll(testPath) + } + + dbPath := path.Join(testPath, "test.db") + db, err := Init(dbPath) + if err != nil { + closeFn() + return nil, nil, err + } + + _, err = db.AddMember(&MemberReq{ + Member: member, + }) + return db, closeFn, err +} diff --git a/api/db/notifications.go b/api/db/notifications.go new file mode 100644 index 0000000..1c956b9 --- /dev/null +++ b/api/db/notifications.go @@ -0,0 +1,87 @@ +package db + +import ( + "log" + "sort" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +const ( + maxNotifications = 5 +) + +type Notification struct { + gorm.Model + MemberNum int `gorm:"column:member"` + Member *Member `gorm:"foreignKey:MemberNum;references:Num"` + Type string `json:"type"` + Order *Order `json:"order,omitempty"` + OrderID *uint `json:"-"` +} + +func (d *DB) AddOrderNotification(order Order) { + nums, err := d.listDesktopNotifications() + if err != nil { + log.Println("Error listing desktop notification recipients:", err) + return + } + + for _, num := range nums { + if num == order.MemberNum { + continue + } + + err = d.addNotification(&Notification{ + MemberNum: num, + Type: "order", + Order: &order, + }) + if err != nil { + log.Println("Error adding order", order.ID, "notification for", num, ":", err) + } + } +} + +func (d *DB) addNotification(notification *Notification) error { + var notifications []Notification + err := d.db.Where("member = ?", notification.MemberNum).Find(¬ifications).Error + if err != nil { + return err + } + + if len(notifications) >= maxNotifications { + sort.Slice(notifications, func(i, j int) bool { + return notifications[i].CreatedAt.Before(notifications[j].CreatedAt) + }) + + for i := 0; i <= len(notifications)-maxNotifications; i++ { + err = d.db.Delete(notifications[i]).Error + if err != nil { + log.Println("Error deleting notification", notifications[i].ID, ":", err) + } + } + } + return d.db.Create(notification).Error +} + +func (d *DB) GetNotifications(num int) []Notification { + var notifications []Notification + err := d.db.Where("member = ?", num). + Preload(clause.Associations). + Find(¬ifications).Error + if err != nil { + log.Println("Error getting notifications:", err) + return notifications + } + + if len(notifications) != 0 { + err = d.db.Where("member = ?", num).Delete(Notification{}).Error + if err != nil { + log.Println("Error flushing notifications:", err) + } + } + + return notifications +} diff --git a/api/db/notifications_test.go b/api/db/notifications_test.go new file mode 100644 index 0000000..7fc314d --- /dev/null +++ b/api/db/notifications_test.go @@ -0,0 +1,113 @@ +package db + +import ( + "testing" +) + +func TestOrderNotification(t *testing.T) { + db, closeFn, err := initTests() + if err != nil { + t.Fatal("Init DB error:", err) + } + defer closeFn() + + var order Order + order.MemberNum = member.Num + 1 + err = db.AddOrder(&order) + if err != nil { + t.Fatal("Error creating order:", err) + } + + notifications := db.GetNotifications(member.Num) + if len(notifications) != 0 { + t.Error("There are notifications:", notifications) + } + db.AddOrderNotification(order) + notifications = db.GetNotifications(member.Num) + if len(notifications) != 1 { + t.Error("Wrong nubmer of notifications:", notifications) + } + notifications = db.GetNotifications(member.Num) + if len(notifications) != 0 { + t.Error("There are notifications:", notifications) + } +} + +func TestMaxNotifications(t *testing.T) { + db, closeFn, err := initTests() + if err != nil { + t.Fatal("Init DB error:", err) + } + defer closeFn() + + var order Order + order.MemberNum = member.Num + 1 + err = db.AddOrder(&order) + if err != nil { + t.Fatal("Error creating order:", err) + } + + for i := 0; i < maxNotifications+2; i++ { + db.AddOrderNotification(order) + } + notifications := db.GetNotifications(member.Num) + if len(notifications) != maxNotifications { + t.Error("Wrong nubmer of notifications:", len(notifications), notifications) + } +} + +func TestNotSelfOrderNotificatino(t *testing.T) { + db, closeFn, err := initTests() + if err != nil { + t.Fatal("Init DB error:", err) + } + defer closeFn() + + var order Order + order.MemberNum = member.Num + err = db.AddOrder(&order) + if err != nil { + t.Fatal("Error creating order:", err) + } + + db.AddOrderNotification(order) + notifications := db.GetNotifications(member.Num) + if len(notifications) != 0 { + t.Error("There are notifications:", notifications) + } +} + +func TestDisabledNotifications(t *testing.T) { + db, closeFn, err := initTests() + if err != nil { + t.Fatal("Init DB error:", err) + } + defer closeFn() + + var order Order + order.MemberNum = member.Num + 1 + err = db.AddOrder(&order) + if err != nil { + t.Fatal("Error creating order:", err) + } + + err = db.SetPreferences(member.Num, "disable_desktop_notifications", true) + if err != nil { + t.Fatal("Can't set preferences:", err) + } + db.AddOrderNotification(order) + notifications := db.GetNotifications(member.Num) + if len(notifications) != 0 { + t.Error("There are notifications:", notifications) + } + + err = db.SetPreferences(member.Num, "disable_desktop_notifications", false) + if err != nil { + t.Fatal("Can't set preferences:", err) + } + db.AddOrderNotification(order) + notifications = db.GetNotifications(member.Num) + if len(notifications) != 1 { + t.Error("Wrong nubmer of notifications:", len(notifications), notifications) + } +} diff --git a/api/db/preferences.go b/api/db/preferences.go new file mode 100644 index 0000000..6c6e88d --- /dev/null +++ b/api/db/preferences.go @@ -0,0 +1,59 @@ +package db + +import ( + "errors" + + "gorm.io/gorm" +) + +type Preferences struct { + gorm.Model + MemberNum int `gorm:"column:member"` + Member *Member `gorm:"foreignKey:MemberNum;references:Num"` + + DisableEmailNotifications bool `json:"disable_email_notifications"` + DisableDesktopNotifications bool `json:"disable_desktop_notifications"` +} + +func (d DB) GetPreferences(num int) (preferences Preferences, err error) { + err = d.db.Where("member = ?", num).First(&preferences).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + err = nil + } + return +} + +func (d DB) SetPreferences(num int, column string, value bool) error { + var preferences Preferences + err := d.db.Where("member = ?", num).First(&preferences).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + preferences.MemberNum = num + switch column { + case "disable_email_notifications": + preferences.DisableEmailNotifications = value + case "disable_desktop_notifications": + preferences.DisableDesktopNotifications = value + } + return d.db.Create(&preferences).Error + } + + return d.db.Model(&preferences).Where("member = ?", num).Update(column, value).Error +} + +func (d DB) ListNotificationEmails() (emails []string, err error) { + err = d.db.Model(&Member{}). + Select("email"). + Joins("left join preferences on preferences.member = members.num"). + Where("disable_email_notifications = false OR preferences.id IS NULL"). + Find(&emails).Error + return +} + +func (d DB) listDesktopNotifications() (nums []int, err error) { + err = d.db.Model(&Member{}). + Select("num"). + Joins("left join preferences on preferences.member = members.num"). + Where("disable_desktop_notifications = false OR preferences.id IS NULL"). + Find(&nums).Error + return +} diff --git a/api/db/preferences_test.go b/api/db/preferences_test.go new file mode 100644 index 0000000..30b797e --- /dev/null +++ b/api/db/preferences_test.go @@ -0,0 +1,53 @@ +package db + +import ( + "testing" +) + +func TestListNotficationEmails(t *testing.T) { + db, closeFn, err := initTests() + if err != nil { + t.Fatal("Init DB error:", err) + } + defer closeFn() + + emails, err := db.ListNotificationEmails() + if err != nil { + t.Fatal("Can't list emails:", err) + } + if len(emails) != 1 { + t.Fatal("Unexpected number of emails:", emails) + } + if emails[0] != member.Email { + t.Error("Wrong email:", emails[0]) + } + + err = db.SetPreferences(member.Num, "disable_email_notifications", true) + if err != nil { + t.Fatal("Can't set preferences:", err) + } + + emails, err = db.ListNotificationEmails() + if err != nil { + t.Fatal("Can't list emails:", err) + } + if len(emails) != 0 { + t.Fatal("Unexpected number of emails:", emails) + } + + err = db.SetPreferences(member.Num, "disable_email_notifications", false) + if err != nil { + t.Fatal("Can't set preferences:", err) + } + + emails, err = db.ListNotificationEmails() + if err != nil { + t.Fatal("Can't list emails:", err) + } + if len(emails) != 1 { + t.Fatal("Unexpected number of emails:", emails) + } + if emails[0] != member.Email { + t.Error("Wrong email:", emails[0]) + } +} diff --git a/api/mail.go b/api/mail.go index 93f8bcc..61e50b8 100644 --- a/api/mail.go +++ b/api/mail.go @@ -2,6 +2,8 @@ package api import ( "bytes" + "fmt" + "log" "net/smtp" "strings" "text/template" @@ -28,6 +30,24 @@ Las siguientes personas han pedido: * {{.OrderProduct.Product.Name}}: {{.Amount}}{{end}} {{end}} +Salud y garbancicos. +` + orderNotifTmpl = `To: {{.To}} +From: {{.From}} +Content-Type: text/plain; charset="utf-8" +Subject: [garbanzo] abierto pedido de {{.Order.Name}} + +Se acaba de abrir el pedido de {{.Order.Name}}. +{{if .Order.Description}} +""" +{{.Order.Description}} +""" +{{end}} +El pedido va a estar abierto hasta: +{{.Order.Deadline}} + +{{.Link}} + Salud y garbancicos. ` passwordResetTmpl = `To: {{.To}} @@ -74,6 +94,7 @@ func NewMail(email, password, server, baseURL string) *Mail { hostname := strings.Split(server, ":")[0] username := strings.Split(email, "@")[0] tmpl := template.Must(template.New("order").Parse(orderTmpl)) + template.Must(tmpl.New("order_notif").Parse(orderNotifTmpl)) template.Must(tmpl.New("password_reset").Parse(passwordResetTmpl)) template.Must(tmpl.New("new_member").Parse(newMemberTmpl)) @@ -130,6 +151,32 @@ func (m Mail) sendOrder(to string, order *db.Order) error { return smtp.SendMail(m.server, m.auth, m.email, []string{to}, buff.Bytes()) } +func (m Mail) sendOrderNotification(order db.Order, emails []string) { + var data struct { + To string + From string + Order *db.Order + Link string + } + data.From = m.email + data.Order = &order + data.Link = fmt.Sprintf("%s/order/%d", m.baseURL, order.ID) + + for _, to := range emails { + data.To = to + var buff bytes.Buffer + err := m.tmpl.ExecuteTemplate(&buff, "order_notif", data) + if err != nil { + log.Printf("Error templating emails order notification: %v", err) + continue + } + err = smtp.SendMail(m.server, m.auth, m.email, []string{to}, buff.Bytes()) + if err != nil { + log.Printf("Error error sending emails order notification: %v", err) + } + } +} + type passwordResetData struct { To string From string diff --git a/api/member.go b/api/member.go index 455f9ac..e4db581 100644 --- a/api/member.go +++ b/api/member.go @@ -178,3 +178,40 @@ func (a *api) UpdateMemberMe(num int, w http.ResponseWriter, req *http.Request) return } } + +func (a *api) GetPreferences(num int, w http.ResponseWriter, req *http.Request) { + preferences, err := a.db.GetPreferences(num) + if err != nil { + log.Printf("Can't get preferences %d: %v", num, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(preferences) + if err != nil { + log.Printf("Can't encode preferences: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func (a *api) SetPreferences(num int, w http.ResponseWriter, req *http.Request) { + var preferences map[string]bool + err := json.NewDecoder(req.Body).Decode(&preferences) + if err != nil { + log.Printf("Can't decode preferences: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + for column, value := range preferences { + err = a.db.SetPreferences(num, column, value) + if err != nil { + log.Printf("Can't update preference %s: %v", column, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + } + w.WriteHeader(http.StatusAccepted) +} diff --git a/api/member_test.go b/api/member_test.go index 10db059..7c11c90 100644 --- a/api/member_test.go +++ b/api/member_test.go @@ -160,6 +160,82 @@ func TestMemberUpdateMe(t *testing.T) { } } +func TestEmptyPreferences(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + tapi.addTestMember() + + var preferences db.Preferences + resp := tapi.do("GET", "/preferences", nil, &preferences) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get preferences:", resp.Status) + } + if preferences.DisableDesktopNotifications { + t.Error("Unexpected desktop notification value") + } + if preferences.DisableEmailNotifications { + t.Error("Unexpected email notification value") + } +} + +func TestSetPreferences(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + tapi.addTestMember() + + prefs := map[string]bool{"disable_email_notifications": true} + resp := tapi.do("PUT", "/preferences", &prefs, nil) + if resp.StatusCode != http.StatusAccepted { + t.Fatal("Can't update preferences:", resp.Status) + } + + var preferences db.Preferences + resp = tapi.do("GET", "/preferences", nil, &preferences) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get preferences:", resp.Status) + } + if preferences.DisableDesktopNotifications { + t.Error("Unexpected desktop notification value") + } + if !preferences.DisableEmailNotifications { + t.Error("Unexpected email notification value") + } + + prefs = map[string]bool{"disable_desktop_notifications": true} + resp = tapi.do("PUT", "/preferences", &prefs, nil) + if resp.StatusCode != http.StatusAccepted { + t.Fatal("Can't update preferences:", resp.Status) + } + + resp = tapi.do("GET", "/preferences", nil, &preferences) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get preferences:", resp.Status) + } + if !preferences.DisableDesktopNotifications { + t.Error("Unexpected desktop notification value") + } + if !preferences.DisableEmailNotifications { + t.Error("Unexpected email notification value") + } + + prefs = map[string]bool{"disable_email_notifications": false} + resp = tapi.do("PUT", "/preferences", &prefs, nil) + if resp.StatusCode != http.StatusAccepted { + t.Fatal("Can't update preferences:", resp.Status) + } + + resp = tapi.do("GET", "/preferences", nil, &preferences) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get preferences:", resp.Status) + } + if !preferences.DisableDesktopNotifications { + t.Error("Unexpected desktop notification value") + } + if preferences.DisableEmailNotifications { + t.Error("Unexpected email notification value") + } +} + func (tapi *testAPI) addTestMember() { resp := tapi.doAdmin("POST", "/member", testMember, nil) if resp.StatusCode != http.StatusCreated { diff --git a/api/order.go b/api/order.go index 259aa47..62a8a65 100644 --- a/api/order.go +++ b/api/order.go @@ -144,6 +144,14 @@ func (a *api) AddOrder(num int, w http.ResponseWriter, req *http.Request) { return } + a.db.AddOrderNotification(order) + emails, err := a.db.ListNotificationEmails() + if err != nil { + log.Printf("Error listing emails to send order notification: %v", err) + } else { + a.mail.sendOrderNotification(order, emails) + } + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) err = json.NewEncoder(w).Encode(order) diff --git a/src/App.js b/src/App.js index 5cb9bf2..88c18c4 100644 --- a/src/App.js +++ b/src/App.js @@ -2,6 +2,7 @@ import React, { useEffect } from "react"; import { useStorageItem } from "@capacitor-community/react-hooks/storage"; import Panel from "./Panel"; import AuthContext from "./AuthContext"; +import notify from "./notifications"; import { ResponseError } from "./errors"; import { url } from "./util"; @@ -18,13 +19,16 @@ function App() { if (data.role !== undefined) { setRole(data.role); } + if (data.notifications) { + data.notifications.forEach((n) => notify(n)); + } }; - const timerID = window.setInterval( + const timerID = setInterval( () => getUpdates(token, setUpdatesData), - 60000 // every minute + 6000 // every minute ); - return () => window.clearInterval(timerID); + return () => clearInterval(timerID); }, [token, setToken, setRole]); const login = (newToken, member) => { diff --git a/src/Head.js b/src/Head.js index 9934e1f..6c89c01 100644 --- a/src/Head.js +++ b/src/Head.js @@ -99,9 +99,12 @@ function Head(props) { <Nav className="ml-auto" activeKey={location.pathname}> <NavDropdown title="Ajustes" id="ajustes"> - <LinkContainer to="/password"> + <LinkContainer to="/preferences/password"> <NavDropdown.Item>Cambiar contraseƱa</NavDropdown.Item> </LinkContainer> + <LinkContainer to="/preferences/notifications"> + <NavDropdown.Item>Notificaciones</NavDropdown.Item> + </LinkContainer> </NavDropdown> {adminNav} diff --git a/src/Panel.js b/src/Panel.js index 9a7e68f..8cb864e 100644 --- a/src/Panel.js +++ b/src/Panel.js @@ -11,7 +11,8 @@ import Inventary from "./inventary/Inventary"; import ShowInventary from "./inventary/ShowInventary"; import CreateSupplier from "./inventary/CreateSupplier"; import Dashboard from "./Dashboard"; -import OwnPassword from "./OwnPassword"; +import OwnPassword from "./preferences/OwnPassword"; +import Notifications from "./preferences/Notifications"; import Purchase from "./purchase/Purchase"; import Topup from "./Topup"; import ShowTransaction from "./transaction/ShowTransaction"; @@ -21,8 +22,8 @@ import CreateOrder from "./order/CreateOrder"; import OrderList from "./order/OrderList"; import EditOrder from "./order/EditOrder"; import SignIn from "./SignIn"; -import ResetRequest from "./ResetRequest"; -import ResetPassword from "./ResetPassword"; +import ResetRequest from "./preferences/ResetRequest"; +import ResetPassword from "./preferences/ResetPassword"; import Head from "./Head"; import logo from "./logo.svg"; @@ -76,9 +77,12 @@ function LogedPanel(props) { <Route path="/transaction"> <TransactionList /> </Route> - <Route path="/password"> + <Route path="/preferences/password"> <OwnPassword /> </Route> + <Route path="/preferences/notifications"> + <Notifications /> + </Route> <Route path="/purchase"> <Purchase /> </Route> diff --git a/src/notifications.js b/src/notifications.js index 8f1ad92..eb82fa0 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -1,13 +1,46 @@ import { Plugins } from "@capacitor/core"; +import { useHistory } from "react-router-dom"; + const { LocalNotifications } = Plugins; +LocalNotifications.addListener("localNotificationActionPerformed", (action) => { + const history = useHistory(); + const splited_id = action.notification.id.split("-"); + if (splited_id.length !== 2) { + return; + } + + const type = splited_id[0]; + const id = splited_id[1]; + switch (type) { + case "order": + history.push("/order/" + id); + break; + default: + return; + } +}); + +function notify(notification) { + let title = ""; + let body = ""; + let id = ""; + + switch (notification.type) { + case "order": + title = "Pedido de " + notification.order.name; + body = notification.order.description; + id = "order-" + notification.order.ID; + break; + default: + return; + } -function notify(text) { LocalNotifications.schedule({ notifications: [ { - title: "Hello", - body: text, - id: Date.now(), + title, + body, + id, smallIcon: "@mipmap/ic_launcher", sound: null, attachments: null, diff --git a/src/preferences/Notifications.js b/src/preferences/Notifications.js new file mode 100644 index 0000000..7b8944d --- /dev/null +++ b/src/preferences/Notifications.js @@ -0,0 +1,71 @@ +import React, { useState, useContext } from "react"; +import { Form } from "react-bootstrap"; +import Fetcher from "../Fetcher"; +import AuthContext from "../AuthContext"; +import { url } from "../util"; + +function send(column, value, token) { + let bodyObj = {}; + bodyObj[column] = value; + const body = JSON.stringify(bodyObj); + + fetch(url("/api/preferences"), { + headers: { "x-authentication": token }, + method: "PUT", + body, + }).then((response) => { + if (!response.ok) { + console.log( + "Failed to send preference: " + + response.status.toString() + + " " + + response.statusText + ); + } + }); +} + +function OwnPassword() { + const auth = useContext(AuthContext); + const [email, setEmail] = useState(true); + const [desktop, setDesktop] = useState(true); + + const setPreferences = (p) => { + setEmail(!p.disable_email_notifications); + setDesktop(!p.disable_desktop_notifications); + }; + + const sendDesktop = (value) => { + console.log("send desktop"); + send("disable_desktop_notifications", !value, auth.token); + setDesktop(value); + }; + const sendEmail = (value) => { + send("disable_email_notifications", !value, auth.token); + setEmail(value); + }; + + return ( + <Fetcher url="/api/preferences" onFetch={setPreferences}> + <h2 className="text-center">Ajustes de notificaciones</h2> + <Form> + <Form.Check + id="email" + type="switch" + label="Recivir notificaciones por email" + onChange={(e) => sendEmail(e.target.checked)} + checked={email} + /> + <Form.Check + id="desktop" + type="switch" + label="Recivir notificaciones en la app" + onChange={(e) => sendDesktop(e.target.checked)} + checked={desktop} + /> + </Form> + </Fetcher> + ); +} + +export default OwnPassword; diff --git a/src/OwnPassword.js b/src/preferences/OwnPassword.js similarity index 97% rename from src/OwnPassword.js rename to src/preferences/OwnPassword.js index 9d6c899..433a782 100644 --- a/src/OwnPassword.js +++ b/src/preferences/OwnPassword.js @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Form, Col, Row, Button, Alert } from "react-bootstrap"; import PasswordForm from "./PasswordForm"; -import Sender from "./Sender"; +import Sender from "../Sender"; function OwnPassword() { const [old_password, setOldPassword] = useState(""); diff --git a/src/PasswordForm.js b/src/preferences/PasswordForm.js similarity index 100% rename from src/PasswordForm.js rename to src/preferences/PasswordForm.js diff --git a/src/ResetPassword.js b/src/preferences/ResetPassword.js similarity index 97% rename from src/ResetPassword.js rename to src/preferences/ResetPassword.js index 4443b1a..8135117 100644 --- a/src/ResetPassword.js +++ b/src/preferences/ResetPassword.js @@ -2,8 +2,8 @@ import React, { useState, useEffect } from "react"; import { Link, useParams } from "react-router-dom"; import { Alert, Form, Col, Row, Button, Spinner } from "react-bootstrap"; import PasswordForm from "./PasswordForm"; -import Sender from "./Sender"; -import { url } from "./util"; +import Sender from "../Sender"; +import { url } from "../util"; function ResetPassword() { const [password, setPassword] = useState(""); diff --git a/src/ResetRequest.js b/src/preferences/ResetRequest.js similarity index 97% rename from src/ResetRequest.js rename to src/preferences/ResetRequest.js index d69f54b..1ad371b 100644 --- a/src/ResetRequest.js +++ b/src/preferences/ResetRequest.js @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { Row, Form, Button, Alert } from "react-bootstrap"; -import Sender from "./Sender"; +import Sender from "../Sender"; function ResetRequest() { const [email, setEmail] = useState(""); -- GitLab