diff --git a/.gitignore b/.gitignore index 422b4ba..9f51806 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ build/ node_modules frontend/dist frontend/wailsjs + +.prettierignore \ No newline at end of file diff --git a/NOTES.md b/NOTES.md index 6b36ea5..9b49b98 100644 --- a/NOTES.md +++ b/NOTES.md @@ -1,5 +1,16 @@ # Doing +Create loading indicator before API is called + +If api query returns error: +- stop interval +- show error to user +- wait for user to press "retry" button to restart interval + +Settings +- allow user to change api key +- allow user to change api interval time + Get user's: username, password, stream key Query API Display followers, subscribers, etc. @@ -7,4 +18,4 @@ Display followers, subscribers, etc. User settings: - API query timer (default: 2s) -# To Do \ No newline at end of file +# To Do diff --git a/app.go b/app.go index 9b538a0..d5d9339 100644 --- a/app.go +++ b/app.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/tylertravisty/go-utils/random" + rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go" ) // App struct @@ -33,3 +34,16 @@ func (a *App) Greet(name string) string { //return fmt.Sprintf("Hello %s, It's show time!", name) return fmt.Sprintf("Hello %s, It's show time!", random) } + +func (a *App) QueryAPI(url string) (*rumblelivestreamlib.Followers, error) { + fmt.Println("QueryAPI") + client := rumblelivestreamlib.Client{StreamKey: url} + resp, err := client.Request() + if err != nil { + // TODO: log error + fmt.Println("client.Request err:", err) + return nil, fmt.Errorf("API request failed") + } + + return &resp.Followers, nil +} diff --git a/frontend/src/assets/icons/eye-slash.png b/frontend/src/assets/icons/eye-slash.png new file mode 100644 index 0000000..fe97fa0 Binary files /dev/null and b/frontend/src/assets/icons/eye-slash.png differ diff --git a/frontend/src/assets/icons/eye.png b/frontend/src/assets/icons/eye.png new file mode 100644 index 0000000..5ca94ec Binary files /dev/null and b/frontend/src/assets/icons/eye.png differ diff --git a/frontend/src/assets/icons/index.jsx b/frontend/src/assets/icons/index.jsx new file mode 100644 index 0000000..c4c8028 --- /dev/null +++ b/frontend/src/assets/icons/index.jsx @@ -0,0 +1,5 @@ +import eye from './eye.png'; +import eye_slash from './eye-slash.png'; + +export const Eye = eye; +export const EyeSlash = eye_slash; diff --git a/frontend/src/screens/Dashboard.css b/frontend/src/screens/Dashboard.css index c8d96c9..fba8777 100644 --- a/frontend/src/screens/Dashboard.css +++ b/frontend/src/screens/Dashboard.css @@ -3,6 +3,80 @@ background-color: #f3f5f8; display: flex; flex-direction: column; - justify-content: space-between; height: 100vh; + width: 100%; +} + +.followers { + background-color: #061726; + height: 100%; + width: 100%; +} + +.followers-list { + overflow-y: auto; +} + +.followers-list-follower { + border-bottom: 1px solid #82b1ff; + color: white; + display: flex; + flex-direction: row; + font-family: sans-serif; + justify-content: space-between; + padding: 10px 20px; +} + +.followers-list-follower-username { + font-weight: bold; +} + +.followers-list-follower-date { + font-family: monospace; +} + +.followers-header { + align-items: center; + border-bottom: 1px solid #82b1ff; + display: flex; + flex-direction: column; + justify-content: space-evenly; + padding: 10px 20px; +} + +.followers-header-title { + color: white; + font-family: sans-serif; + font-size: 24px; + font-weight: bold; + padding-bottom: 10px; + text-transform: uppercase; +} + +.followers-header-highlights { + display: flex; + flex-direction: row; + justify-content: space-evenly; + width: 100%; +} + +.followers-header-highlight { + align-items: center; + color: white; + display: flex; + background-color: #75a54b; + border-radius: 0.5rem; + flex-direction: column; + font-family: sans-serif; + font-weight: bold; + min-width: 50px; + padding: 10px; +} + +.followers-header-highlight-count { + font-size: 16px; +} + +.followers-header-highlight-description { + font-size: 12px; } \ No newline at end of file diff --git a/frontend/src/screens/Dashboard.jsx b/frontend/src/screens/Dashboard.jsx index 5a45a4e..599e0e6 100644 --- a/frontend/src/screens/Dashboard.jsx +++ b/frontend/src/screens/Dashboard.jsx @@ -1,14 +1,162 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; +import { QueryAPI } from '../../wailsjs/go/main/App'; import './Dashboard.css'; function Dashboard() { const location = useLocation(); const [streamKey, setStreamKey] = useState(location.state.streamKey); + const [followers, setFollowers] = useState({}); + const [totalFollowers, setTotalFollowers] = useState('-'); + const [channelFollowers, setChannelFollowers] = useState('-'); + const [latestFollower, setLatestFollower] = useState('-'); + const [recentFollowers, setRecentFollowers] = useState([]); + + // useEffect(() => { + // QueryAPI(streamKey) + // .then((response) => { + // console.log(response); + // setFollowers(response); + // setChannelFollowers(response.num_followers); + // setTotalFollowers(response.num_followers_total); + // setLatestFollower(response.latest_follower.username); + // setRecentFollowers(response.recent_followers); + // }) + // .catch((e) => console.log('Error:', e)); + // }, []); + + useEffect(() => { + let interval = setInterval(() => { + console.log('Query API'); + QueryAPI(streamKey) + .then((response) => { + console.log(response); + setFollowers(response); + setChannelFollowers(response.num_followers); + setTotalFollowers(response.num_followers_total); + setLatestFollower(response.latest_follower.username); + setRecentFollowers(response.recent_followers); + }) + .catch((e) => console.log('Error:', e)); + }, 10000); + + return () => { + clearInterval(interval); + }; + }, []); + + const dateDate = (date) => { + const options = { month: 'short' }; + let month = new Intl.DateTimeFormat('en-US', options).format(date); + let day = date.getDay(); + return month + ' ' + day; + }; + + const dateDay = (date) => { + let now = new Date(); + let today = now.getDay(); + switch (date.getDay()) { + case 0: + return 'Sunday'; + case 1: + return 'Monday'; + case 2: + return 'Tuesday'; + case 3: + return 'Wednesday'; + case 4: + return 'Thursday'; + case 5: + return 'Friday'; + case 6: + return 'Saturday'; + } + }; + + const dateTime = (date) => { + let now = new Date(); + let today = now.getDay(); + let day = date.getDay(); + + if (today !== day) { + return dateDay(date); + } + + let hours24 = date.getHours(); + let hours = hours24 % 12 || 12; + + let minutes = date.getMinutes(); + + let mer = 'pm'; + if (hours24 < 12) { + mer = 'am'; + } + + return hours + ':' + minutes + ' ' + mer; + }; + + const dateString = (d) => { + let now = new Date(); + let date = new Date(d); + // Fix Rumble's timezone problem + date.setHours(date.getHours() - 4); + let diff = now - date; + switch (true) { + case diff < 60000: + return 'Now'; + case diff < 3600000: + let minutes = Math.floor(diff / 1000 / 60); + let postfix = ' minutes ago'; + if (minutes == 1) { + postfix = ' minute ago'; + } + return minutes + postfix; + case diff < 86400000: + return dateTime(date); + case diff < 604800000: + return dateDay(date); + default: + return dateDate(date); + } + console.log('Diff:', diff); + return d; + }; + return (