- Add client TZ support + Selectable on user page
- Move token auth to GET ?key=XYZ for wider webhook support
- Add Multiscrobbler support
- Add /api/v1/serverinfo for version information
This commit is contained in:
Daniel Mason 2021-04-03 01:11:05 +13:00
parent 9866dea36b
commit 67e43b8984
Signed by: idanoo
GPG Key ID: 387387CDBC02F132
14 changed files with 415 additions and 114 deletions

View File

@ -1,3 +1,9 @@
# 0.0.12
- Add client TZ support + Selectable on user page
- Move token auth to GET ?key=XYZ for wider webhook support
- Add Multiscrobbler support
- Add /api/v1/serverinfo for version information
# 0.0.11 # 0.0.11
- Fix redirects to /login for auth required pages - Fix redirects to /login for auth required pages
- Add handling for 401/429 + No connection responses in API calls - Add handling for 401/429 + No connection responses in API calls
@ -22,7 +28,7 @@
# 0.0.8 # 0.0.8
- Added Admin/Site config page in frontend for admin users - Added Admin/Site config page in frontend for admin users
- Added API POST/GET /config endpoint - Added API POST/GET /config endpointnpm install react-select-timezone
# 0.0.7 # 0.0.7
- Switch redux -> Context - Switch redux -> Context

View File

@ -11,6 +11,10 @@ type Config struct {
Setting map[string]string `json:"configs"` Setting map[string]string `json:"configs"`
} }
type ServerInfo struct {
Version string `json:"version"`
}
func getAllConfigs() (Config, error) { func getAllConfigs() (Config, error) {
config := Config{} config := Config{}
configs := make(map[string]string) configs := make(map[string]string)

View File

@ -4,63 +4,70 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"net" "net"
"time"
) )
type MultiScrobblerInput struct {
Artists []string `json:"artists"`
Album string `json:"album"`
Track string `json:"track"`
PlayedAt time.Time `json:"playDate"`
Duration string `json:"duration"`
}
// ParseMultiScrobblerInput - Transform API data // ParseMultiScrobblerInput - Transform API data
func ParseMultiScrobblerInput(userUUID string, data map[string]interface{}, ip net.IP, tx *sql.Tx) error { func ParseMultiScrobblerInput(userUUID string, data map[string]interface{}, ip net.IP, tx *sql.Tx) error {
// Debugging // Debugging
fmt.Printf("%+v", data) fmt.Printf("%+v", data)
// if data["ItemType"] != "Audio" {
// return errors.New("Media type not audio")
// }
// // Safety Checks // // Safety Checks
// if data["Artist"] == nil { // if data["artists"] == nil {
// return errors.New("Missing artist data") // return errors.New("Missing artist data")
// } // }
// if data["Album"] == nil { // if data["album"] == nil {
// return errors.New("Missing album data") // return errors.New("Missing album data")
// } // }
// if data["Name"] == nil { // if data["track"] == nil {
// return errors.New("Missing track data") // return errors.New("Missing track data")
// } // }
// // Insert artist if not exist // // Insert track artists
// artist, err := insertArtist(fmt.Sprintf("%s", data["Artist"]), fmt.Sprintf("%s", data["Provider_musicbrainzartist"]), tx) // for _, artist := range data["artists"] {
// artist, err := insertArtist(artist.Name, "", artist.ID.String(), tx)
// if err != nil { // if err != nil {
// log.Printf("%+v", err) // log.Printf("%+v", err)
// return errors.New("Failed to map artist") // return errors.New("Failed to map artist: " + artist.Name)
// }
// artists = append(artists, artist.Uuid)
// } // }
// // Insert album if not exist // // Insert album if not exist
// artists := []string{artist.Uuid} // artists := []string{artist.Uuid}
// album, err := insertAlbum(fmt.Sprintf("%s", data["Album"]), fmt.Sprintf("%s", data["Provider_musicbrainzalbum"]), artists, tx) // album, err := insertAlbum(fmt.Sprintf("%s", data["Album"]), fmt.Sprintf("%s", data["Provider_musicbrainzalbum"]), "", artists, tx)
// if err != nil { // if err != nil {
// log.Printf("%+v", err) // log.Printf("%+v", err)
// return errors.New("Failed to map album") // return errors.New("Failed to map album")
// } // }
// // Insert album if not exist // // Insert track if not exist
// track, err := insertTrack(fmt.Sprintf("%s", data["Name"]), fmt.Sprintf("%s", data["Provider_musicbrainztrack"]), album.Uuid, artists, tx) // length := timestampToSeconds(fmt.Sprintf("%s", data["RunTime"]))
// track, err := insertTrack(fmt.Sprintf("%s", data["Name"]), length, fmt.Sprintf("%s", data["Provider_musicbrainztrack"]), "", album.Uuid, artists, tx)
// if err != nil { // if err != nil {
// log.Printf("%+v", err) // log.Printf("%+v", err)
// return errors.New("Failed to map track") // return errors.New("Failed to map track")
// } // }
// // Insert album if not exist // // Insert scrobble if not exist
// err = insertScrobble(userUUID, track.Uuid, "jellyfin", ip, tx) // timestamp := time.Now()
// fmt.Println(timestamp)
// err = insertScrobble(userUUID, track.Uuid, "jellyfin", timestamp, ip, tx)
// if err != nil { // if err != nil {
// log.Printf("%+v", err) // log.Printf("%+v", err)
// return errors.New("Failed to map track") // return errors.New("Failed to map track")
// } // }
// _ = album
// _ = artist
// _ = track
// Insert track if not exist // Insert track if not exist
return nil return nil
} }

View File

@ -124,7 +124,6 @@ func (user *User) updateSpotifyPlaydata() {
break break
} }
tx.Commit() tx.Commit()
fmt.Printf("Updated spotify track: %+v", v.Track.Name)
} }
} }

View File

@ -38,7 +38,7 @@ func HandleRequests(port string) {
// JWT Auth - Own profile only (Uses uuid in JWT) // JWT Auth - Own profile only (Uses uuid in JWT)
v1.HandleFunc("/user", limitMiddleware(jwtMiddleware(fetchUser), lightLimiter)).Methods("GET") v1.HandleFunc("/user", limitMiddleware(jwtMiddleware(fetchUser), lightLimiter)).Methods("GET")
// v1.HandleFunc("/user", jwtMiddleware(fetchScrobbleResponse)).Methods("PATCH") v1.HandleFunc("/user", limitMiddleware(jwtMiddleware(patchUser), lightLimiter)).Methods("PATCH")
v1.HandleFunc("/user/spotify", limitMiddleware(jwtMiddleware(getSpotifyClientID), lightLimiter)).Methods("GET") v1.HandleFunc("/user/spotify", limitMiddleware(jwtMiddleware(getSpotifyClientID), lightLimiter)).Methods("GET")
v1.HandleFunc("/user/spotify", limitMiddleware(jwtMiddleware(deleteSpotifyLink), lightLimiter)).Methods("DELETE") v1.HandleFunc("/user/spotify", limitMiddleware(jwtMiddleware(deleteSpotifyLink), lightLimiter)).Methods("DELETE")
v1.HandleFunc("/user/{uuid}/scrobbles", jwtMiddleware(fetchScrobbleResponse)).Methods("GET") v1.HandleFunc("/user/{uuid}/scrobbles", jwtMiddleware(fetchScrobbleResponse)).Methods("GET")
@ -55,6 +55,7 @@ func HandleRequests(port string) {
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST") v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
v1.HandleFunc("/sendreset", limitMiddleware(handleSendReset, heavyLimiter)).Methods("POST") v1.HandleFunc("/sendreset", limitMiddleware(handleSendReset, heavyLimiter)).Methods("POST")
v1.HandleFunc("/resetpassword", limitMiddleware(handleResetPassword, heavyLimiter)).Methods("POST") v1.HandleFunc("/resetpassword", limitMiddleware(handleResetPassword, heavyLimiter)).Methods("POST")
v1.HandleFunc("/serverinfo", fetchServerInfo).Methods("GET")
// Redirect from Spotify Oauth // Redirect from Spotify Oauth
v1.HandleFunc("/link/spotify", limitMiddleware(postSpotifyReponse, lightLimiter)) v1.HandleFunc("/link/spotify", limitMiddleware(postSpotifyReponse, lightLimiter))
@ -67,11 +68,12 @@ func HandleRequests(port string) {
r.PathPrefix("/").Handler(spa) r.PathPrefix("/").Handler(spa)
c := cors.New(cors.Options{ c := cors.New(cors.Options{
// Grrrr CORS. To clean up at a later date
AllowedOrigins: []string{"*"}, AllowedOrigins: []string{"*"},
AllowCredentials: true, AllowCredentials: true,
AllowedMethods: []string{"GET", "POST", "PATCH", "DELETE"},
AllowedHeaders: []string{"*"}, AllowedHeaders: []string{"*"},
}) })
handler := c.Handler(r) handler := c.Handler(r)
// Serve it up! // Serve it up!
@ -219,6 +221,7 @@ func handleResetPassword(w http.ResponseWriter, r *http.Request) {
// serveEndpoint - API stuffs // serveEndpoint - API stuffs
func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) { func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
bodyJson, err := decodeJson(r.Body) bodyJson, err := decodeJson(r.Body)
fmt.Println(err)
if err != nil { if err != nil {
throwInvalidJson(w) throwInvalidJson(w)
return return
@ -291,6 +294,29 @@ func fetchUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser s
w.Write(json) w.Write(json)
} }
// patchUser - Update specific values
func patchUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser string) {
userFull, err := getUser(jwtUser)
if err != nil {
throwOkError(w, "Failed to fetch user information")
return
}
bodyJson, _ := decodeJson(r.Body)
ip := getUserIp(r)
for k, v := range bodyJson {
val := fmt.Sprintf("%s", v)
if k == "timezone" {
if isValidTimezone(val) {
userFull.updateUser("timezone", val, ip)
}
}
}
throwOkMessage(w, "User updated successfully")
}
// fetchScrobbles - Return an array of scrobbles // fetchScrobbles - Return an array of scrobbles
func fetchScrobbleResponse(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser string) { func fetchScrobbleResponse(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser string) {
resp, err := fetchScrobblesForUser(reqUser, 100, 1) resp, err := fetchScrobblesForUser(reqUser, 100, 1)
@ -408,3 +434,13 @@ func deleteSpotifyLink(w http.ResponseWriter, r *http.Request, u string, v strin
throwOkMessage(w, "Spotify account successfully unlinked") throwOkMessage(w, "Spotify account successfully unlinked")
} }
func fetchServerInfo(w http.ResponseWriter, r *http.Request) {
info := ServerInfo{
Version: "0.0.11",
}
js, _ := json.Marshal(&info)
w.WriteHeader(http.StatusOK)
w.Write(js)
}

View File

@ -20,14 +20,21 @@ var lightLimiter = NewIPRateLimiter(10, 10)
// tokenMiddleware - Validates token to a user // tokenMiddleware - Validates token to a user
func tokenMiddleware(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { func tokenMiddleware(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
fullToken := r.Header.Get("Authorization") key := ""
authToken := strings.Replace(fullToken, "Bearer ", "", 1) urlParams := r.URL.Query()
if authToken == "" { if val, ok := urlParams["key"]; ok {
key = val[0]
} else {
throwUnauthorized(w, "No key parameter provided")
return
}
if key == "" {
throwUnauthorized(w, "A token is required") throwUnauthorized(w, "A token is required")
return return
} }
userUuid, err := getUserUuidForToken(authToken) userUuid, err := getUserUuidForToken(key)
if err != nil { if err != nil {
throwUnauthorized(w, err.Error()) throwUnauthorized(w, err.Error())
return return

View File

@ -156,14 +156,14 @@ func insertUser(username string, email string, password []byte, ip net.IP) error
return err return err
} }
func updateUser(uuid string, field string, value string, ip net.IP) error { func (user *User) updateUser(field string, value string, ip net.IP) error {
_, err := db.Exec("UPDATE users SET `"+field+"` = ?, modified_at = NOW(), modified_ip = ? WHERE uuid = ?", value, uuid, ip) _, err := db.Exec("UPDATE users SET `"+field+"` = ?, modified_at = NOW(), modified_ip = ? WHERE uuid = UUID_TO_BIN(?, true)", value, ip, user.UUID)
return err return err
} }
func updateUserDirect(uuid string, field string, value string) error { func (user *User) updateUserDirect(field string, value string) error {
_, err := db.Exec("UPDATE users SET `"+field+"` = ? WHERE uuid = ?", value, uuid) _, err := db.Exec("UPDATE users SET `"+field+"` = ? WHERE uuid = UUID_TO_BIN(?, true)", value, user.UUID)
return err return err
} }

View File

@ -10,6 +10,7 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"time"
) )
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
@ -79,6 +80,10 @@ func getUserIp(r *http.Request) net.IP {
} }
} }
if host == "" {
host = "0.0.0.0"
}
ip = net.ParseIP(host) ip = net.ParseIP(host)
return ip return ip
} }
@ -144,3 +149,8 @@ func filterSlice(s []string) []string {
fmt.Printf("RESTULS: %+v", result) fmt.Printf("RESTULS: %+v", result)
return result return result
} }
func isValidTimezone(tz string) bool {
_, err := time.LoadLocation(tz)
return err == nil
}

View File

@ -1,8 +1,8 @@
CREATE TABLE IF NOT EXISTS `users` ( CREATE TABLE IF NOT EXISTS `users` (
`uuid` BINARY(16) PRIMARY KEY, `uuid` BINARY(16) PRIMARY KEY,
`created_at` DATETIME NOT NULL, `created_at` DATETIME NOT NULL DEFAULT NOW(),
`created_ip` VARBINARY(16) NULL DEFAULT NULL, `created_ip` VARBINARY(16) NULL DEFAULT NULL,
`modified_at` DATETIME NOT NULL, `modified_at` DATETIME NOT NULL DEFAULT NOW(),
`modified_ip` VARBINARY(16) NULL DEFAULT NULL, `modified_ip` VARBINARY(16) NULL DEFAULT NULL,
`username` VARCHAR(64) NOT NULL, `username` VARCHAR(64) NOT NULL,
`password` VARCHAR(60) NOT NULL, `password` VARCHAR(60) NOT NULL,

345
web/package-lock.json generated
View File

@ -22,10 +22,10 @@
"react-bootstrap": "^1.5.2", "react-bootstrap": "^1.5.2",
"react-cookie": "^4.0.3", "react-cookie": "^4.0.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-redux": "^7.2.3",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-spinners": "^0.10.6", "react-spinners": "^0.10.6",
"react-timezone-select": "^0.10.7",
"react-toastify": "^7.0.3", "react-toastify": "^7.0.3",
"reactstrap": "^8.9.0" "reactstrap": "^8.9.0"
}, },
@ -1518,6 +1518,66 @@
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
}, },
"node_modules/@emotion/react": {
"version": "11.1.5",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.1.5.tgz",
"integrity": "sha512-xfnZ9NJEv9SU9K2sxXM06lzjK245xSeHRpUh67eARBm3PBHjjKIZlfWZ7UQvD0Obvw6ZKjlC79uHrlzFYpOB/Q==",
"dependencies": {
"@babel/runtime": "^7.7.2",
"@emotion/cache": "^11.1.3",
"@emotion/serialize": "^1.0.0",
"@emotion/sheet": "^1.0.1",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
"@babel/core": "^7.0.0",
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/react/node_modules/@emotion/cache": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.1.3.tgz",
"integrity": "sha512-n4OWinUPJVaP6fXxWZD9OUeQ0lY7DvtmtSuqtRWT0Ofo/sBLCVSgb4/Oa0Q5eFxcwablRKjUXqXtNZVyEwCAuA==",
"dependencies": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.0.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "^4.0.3"
}
},
"node_modules/@emotion/react/node_modules/@emotion/serialize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.1.tgz",
"integrity": "sha512-TXlKs5sgUKhFlszp/rg4lIAZd7UUSmJpwaf9/lAEFcUh2vPi32i7x4wk7O8TN8L8v2Ol8k0CxnhRBY0zQalTxA==",
"dependencies": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
"@emotion/unitless": "^0.7.5",
"@emotion/utils": "^1.0.0",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/react/node_modules/@emotion/sheet": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.1.tgz",
"integrity": "sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g=="
},
"node_modules/@emotion/react/node_modules/@emotion/utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
"integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
},
"node_modules/@emotion/serialize": { "node_modules/@emotion/serialize": {
"version": "0.11.16", "version": "0.11.16",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz",
@ -3390,17 +3450,6 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-redux": {
"version": "7.1.16",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz",
"integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/react-transition-group": { "node_modules/@types/react-transition-group": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz",
@ -13463,6 +13512,11 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/memoize-one": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
"integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
},
"node_modules/memory-fs": { "node_modules/memory-fs": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@ -16713,6 +16767,17 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
}, },
"node_modules/react-input-autosize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz",
"integrity": "sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==",
"dependencies": {
"prop-types": "^15.5.8"
},
"peerDependencies": {
"react": "^16.3.0 || ^17.0.0"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@ -16759,36 +16824,6 @@
"react": "0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0" "react": "0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0"
} }
}, },
"node_modules/react-redux": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.3.tgz",
"integrity": "sha512-ZhAmQ1lrK+Pyi0ZXNMUZuYxYAZd59wFuVDGUt536kSGdD0ya9Q7BfsE95E3TsFLE3kOSFp5m6G5qbatE+Ic1+w==",
"dependencies": {
"@babel/runtime": "^7.12.1",
"@types/react-redux": "^7.1.16",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^16.13.1"
},
"peerDependencies": {
"react": "^16.8.3 || ^17",
"redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-redux/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.8.3", "version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
@ -16973,6 +17008,46 @@
"semver": "bin/semver" "semver": "bin/semver"
} }
}, },
"node_modules/react-select": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.0.tgz",
"integrity": "sha512-SBPD1a3TJqE9zoI/jfOLCAoLr/neluaeokjOixr3zZ1vHezkom8K0A9J4QG9IWDqIDE9K/Mv+0y1GjidC2PDtQ==",
"dependencies": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.0.0",
"@emotion/react": "^11.1.1",
"memoize-one": "^5.0.0",
"prop-types": "^15.6.0",
"react-input-autosize": "^3.0.0",
"react-transition-group": "^4.3.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
}
},
"node_modules/react-select/node_modules/@emotion/cache": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.1.3.tgz",
"integrity": "sha512-n4OWinUPJVaP6fXxWZD9OUeQ0lY7DvtmtSuqtRWT0Ofo/sBLCVSgb4/Oa0Q5eFxcwablRKjUXqXtNZVyEwCAuA==",
"dependencies": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.0.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "^4.0.3"
}
},
"node_modules/react-select/node_modules/@emotion/sheet": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.1.tgz",
"integrity": "sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g=="
},
"node_modules/react-select/node_modules/@emotion/utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
"integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
},
"node_modules/react-spinners": { "node_modules/react-spinners": {
"version": "0.10.6", "version": "0.10.6",
"resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.10.6.tgz", "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.10.6.tgz",
@ -16985,6 +17060,20 @@
"react-dom": "^16.0.0 || ^17.0.0" "react-dom": "^16.0.0 || ^17.0.0"
} }
}, },
"node_modules/react-timezone-select": {
"version": "0.10.7",
"resolved": "https://registry.npmjs.org/react-timezone-select/-/react-timezone-select-0.10.7.tgz",
"integrity": "sha512-JPnrYcXf3NqqNl4HYDsDaS6OiUEPW6i7xeGE1xOA7SPuh7oR5vzKj0g0kbg5IH8LkiWTZNWk5NbBSJkRHp/dlA==",
"dependencies": {
"react-select": "^4.0.2",
"spacetime": "^6.12.3",
"spacetime-informal": "^0.5.0"
},
"peerDependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
},
"node_modules/react-toastify": { "node_modules/react-toastify": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz",
@ -18851,6 +18940,16 @@
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
}, },
"node_modules/spacetime": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/spacetime/-/spacetime-6.14.0.tgz",
"integrity": "sha512-pz/nMIRGNSJeFfDFvhPjMHXhFU1NcrYnpydMuSS2Zsk0NEoHJc2rRKXugkmlqUv/l/fPxWVJVnj8isVS0//vbQ=="
},
"node_modules/spacetime-informal": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/spacetime-informal/-/spacetime-informal-0.5.0.tgz",
"integrity": "sha512-cdSsniJJfJJTBdeVvXtooxyXzrRfoBVjAl3usQl9DgGExB3XN3deA3MwjInnD/26C/lANf3dU54bT2YweAGrOw=="
},
"node_modules/spdx-correct": { "node_modules/spdx-correct": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@ -19408,6 +19507,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/stylis": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.9.tgz",
"integrity": "sha512-ci7pEFNVW3YJiWEzqPOMsAjY6kgraZ3ZgBfQ5HYbNtLJEsQ0G46ejWZpfSSCp/FaSiCSGGhzL9O2lN+2cB6ong=="
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -23661,6 +23765,56 @@
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
}, },
"@emotion/react": {
"version": "11.1.5",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.1.5.tgz",
"integrity": "sha512-xfnZ9NJEv9SU9K2sxXM06lzjK245xSeHRpUh67eARBm3PBHjjKIZlfWZ7UQvD0Obvw6ZKjlC79uHrlzFYpOB/Q==",
"requires": {
"@babel/runtime": "^7.7.2",
"@emotion/cache": "^11.1.3",
"@emotion/serialize": "^1.0.0",
"@emotion/sheet": "^1.0.1",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
},
"dependencies": {
"@emotion/cache": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.1.3.tgz",
"integrity": "sha512-n4OWinUPJVaP6fXxWZD9OUeQ0lY7DvtmtSuqtRWT0Ofo/sBLCVSgb4/Oa0Q5eFxcwablRKjUXqXtNZVyEwCAuA==",
"requires": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.0.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "^4.0.3"
}
},
"@emotion/serialize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.1.tgz",
"integrity": "sha512-TXlKs5sgUKhFlszp/rg4lIAZd7UUSmJpwaf9/lAEFcUh2vPi32i7x4wk7O8TN8L8v2Ol8k0CxnhRBY0zQalTxA==",
"requires": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
"@emotion/unitless": "^0.7.5",
"@emotion/utils": "^1.0.0",
"csstype": "^3.0.2"
}
},
"@emotion/sheet": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.1.tgz",
"integrity": "sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g=="
},
"@emotion/utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
"integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
}
}
},
"@emotion/serialize": { "@emotion/serialize": {
"version": "0.11.16", "version": "0.11.16",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz",
@ -25055,17 +25209,6 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"@types/react-redux": {
"version": "7.1.16",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz",
"integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-transition-group": { "@types/react-transition-group": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz",
@ -32905,6 +33048,11 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
}, },
"memoize-one": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
"integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
},
"memory-fs": { "memory-fs": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@ -35510,6 +35658,14 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
}, },
"react-input-autosize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz",
"integrity": "sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==",
"requires": {
"prop-types": "^15.5.8"
}
},
"react-is": { "react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@ -35549,26 +35705,6 @@
"warning": "^4.0.2" "warning": "^4.0.2"
} }
}, },
"react-redux": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.3.tgz",
"integrity": "sha512-ZhAmQ1lrK+Pyi0ZXNMUZuYxYAZd59wFuVDGUt536kSGdD0ya9Q7BfsE95E3TsFLE3kOSFp5m6G5qbatE+Ic1+w==",
"requires": {
"@babel/runtime": "^7.12.1",
"@types/react-redux": "^7.1.16",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^16.13.1"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
"react-refresh": { "react-refresh": {
"version": "0.8.3", "version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
@ -35723,6 +35859,44 @@
} }
} }
}, },
"react-select": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.0.tgz",
"integrity": "sha512-SBPD1a3TJqE9zoI/jfOLCAoLr/neluaeokjOixr3zZ1vHezkom8K0A9J4QG9IWDqIDE9K/Mv+0y1GjidC2PDtQ==",
"requires": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.0.0",
"@emotion/react": "^11.1.1",
"memoize-one": "^5.0.0",
"prop-types": "^15.6.0",
"react-input-autosize": "^3.0.0",
"react-transition-group": "^4.3.0"
},
"dependencies": {
"@emotion/cache": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.1.3.tgz",
"integrity": "sha512-n4OWinUPJVaP6fXxWZD9OUeQ0lY7DvtmtSuqtRWT0Ofo/sBLCVSgb4/Oa0Q5eFxcwablRKjUXqXtNZVyEwCAuA==",
"requires": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.0.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "^4.0.3"
}
},
"@emotion/sheet": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.1.tgz",
"integrity": "sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g=="
},
"@emotion/utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
"integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
}
}
},
"react-spinners": { "react-spinners": {
"version": "0.10.6", "version": "0.10.6",
"resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.10.6.tgz", "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.10.6.tgz",
@ -35731,6 +35905,16 @@
"@emotion/core": "^10.0.35" "@emotion/core": "^10.0.35"
} }
}, },
"react-timezone-select": {
"version": "0.10.7",
"resolved": "https://registry.npmjs.org/react-timezone-select/-/react-timezone-select-0.10.7.tgz",
"integrity": "sha512-JPnrYcXf3NqqNl4HYDsDaS6OiUEPW6i7xeGE1xOA7SPuh7oR5vzKj0g0kbg5IH8LkiWTZNWk5NbBSJkRHp/dlA==",
"requires": {
"react-select": "^4.0.2",
"spacetime": "^6.12.3",
"spacetime-informal": "^0.5.0"
}
},
"react-toastify": { "react-toastify": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz",
@ -37221,6 +37405,16 @@
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
}, },
"spacetime": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/spacetime/-/spacetime-6.14.0.tgz",
"integrity": "sha512-pz/nMIRGNSJeFfDFvhPjMHXhFU1NcrYnpydMuSS2Zsk0NEoHJc2rRKXugkmlqUv/l/fPxWVJVnj8isVS0//vbQ=="
},
"spacetime-informal": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/spacetime-informal/-/spacetime-informal-0.5.0.tgz",
"integrity": "sha512-cdSsniJJfJJTBdeVvXtooxyXzrRfoBVjAl3usQl9DgGExB3XN3deA3MwjInnD/26C/lANf3dU54bT2YweAGrOw=="
},
"spdx-correct": { "spdx-correct": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@ -37668,6 +37862,11 @@
} }
} }
}, },
"stylis": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.9.tgz",
"integrity": "sha512-ci7pEFNVW3YJiWEzqPOMsAjY6kgraZ3ZgBfQ5HYbNtLJEsQ0G46ejWZpfSSCp/FaSiCSGGhzL9O2lN+2cB6ong=="
},
"supports-color": { "supports-color": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",

View File

@ -17,10 +17,10 @@
"react-bootstrap": "^1.5.2", "react-bootstrap": "^1.5.2",
"react-cookie": "^4.0.3", "react-cookie": "^4.0.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-redux": "^7.2.3",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-spinners": "^0.10.6", "react-spinners": "^0.10.6",
"react-timezone-select": "^0.10.7",
"react-toastify": "^7.0.3", "react-toastify": "^7.0.3",
"reactstrap": "^8.9.0" "reactstrap": "^8.9.0"
}, },

View File

@ -3,18 +3,24 @@ import jwt from 'jwt-decode'
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
function getHeaders() { function getHeaders() {
// Todo: move this to use Context values instead. // TODO: move this to use Context values instead.
const user = JSON.parse(localStorage.getItem('user')); const user = JSON.parse(localStorage.getItem('user'));
if (user && user.jwt) { if (user && user.jwt) {
return { Authorization: 'Bearer ' + user.jwt, changeOrigin: true }; var unixtime = Math.round((new Date()).getTime() / 1000);
if (user.exp < unixtime) {
// TODO: Handle expiry nicer
toast.warning("Session expired. Please log in again")
}
return { Authorization: 'Bearer ' + user.jwt };
} else { } else {
return {}; return {};
} }
} }
function getUserUuid() { function getUserUuid() {
// Todo: move this to use Context values instead. // TODO: move this to use Context values instead.
const user = JSON.parse(localStorage.getItem('user')); const user = JSON.parse(localStorage.getItem('user'));
if (user && user.uuid) { if (user && user.uuid) {
@ -179,6 +185,15 @@ export const getUser = () => {
}); });
}; };
export const patchUser = (values) => {
return axios.patch(process.env.REACT_APP_API_URL + "user", values, { headers: getHeaders() })
.then((data) => {
return data.data;
}).catch((error) => {
return handleErrorResp(error)
});
};
export const validateResetPassword = (tokenStr) => { export const validateResetPassword = (tokenStr) => {
return axios.get(process.env.REACT_APP_API_URL + "user/", { headers: getHeaders() }) return axios.get(process.env.REACT_APP_API_URL + "user/", { headers: getHeaders() })
.then((data) => { .then((data) => {

View File

@ -2,3 +2,8 @@
padding: 20px 5px 5px 5px; padding: 20px 5px 5px 5px;
font-size: 16pt; font-size: 16pt;
} }
.userDropdown {
color: #282C34;
font-size: 12pt;
}

View File

@ -4,9 +4,11 @@ import './User.css';
import { useHistory } from "react-router"; import { useHistory } from "react-router";
import AuthContext from '../Contexts/AuthContext'; import AuthContext from '../Contexts/AuthContext';
import ScaleLoader from 'react-spinners/ScaleLoader'; import ScaleLoader from 'react-spinners/ScaleLoader';
import { getUser } from '../Api/index' import { getUser, patchUser } from '../Api/index'
import { Button } from 'reactstrap'; import { Button } from 'reactstrap';
import { spotifyConnectionRequest, spotifyDisonnectionRequest } from '../Api/index' import { spotifyConnectionRequest, spotifyDisonnectionRequest } from '../Api/index'
import TimezoneSelect from 'react-timezone-select'
const User = () => { const User = () => {
const history = useHistory(); const history = useHistory();
@ -14,6 +16,11 @@ const User = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [userdata, setUserdata] = useState({}); const [userdata, setUserdata] = useState({});
const updateTimezone = (vals) => {
console.log(vals)
setUserdata({...userdata, timezone: vals});
patchUser({timezone: vals.value})
}
useEffect(() => { useEffect(() => {
if (!user) { if (!user) {
@ -45,6 +52,12 @@ const User = () => {
Welcome {userdata.username} Welcome {userdata.username}
</h1> </h1>
<p className="userBody"> <p className="userBody">
Timezone<br/>
<TimezoneSelect
className="userDropdown"
value={userdata.timezone}
onChange={updateTimezone}
/><br/>
Created At: {userdata.created_at}<br/> Created At: {userdata.created_at}<br/>
Email: {userdata.email}<br/> Email: {userdata.email}<br/>
Verified: {userdata.verified ? '✓' : '✖'}<br/> Verified: {userdata.verified ? '✓' : '✖'}<br/>