diff --git a/.vscode/launch.json b/.vscode/launch.json index b8f7b47..7312014 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", - "url": "http://10.0.0.99:3000", + "url": "http://10.0.0.20:3000", "webRoot": "${workspaceFolder}" } ] diff --git a/backend/api.js b/backend/api.js index 364292a..a63d78f 100644 --- a/backend/api.js +++ b/backend/api.js @@ -10,17 +10,35 @@ router.get('/test', async (req, res) => { }); router.get('/getconfig', async (req, res) => { - const { rows } = await db.query('SELECT * FROM app_config'); + const { rows } = await db.query('SELECT * FROM app_config where "ID"=1'); console.log(`ENDPOINT CALLED: /getconfig: `+rows); + // console.log(`ENDPOINT CALLED: /setconfig: `+rows.length); res.send(rows); + }); router.post('/setconfig', async (req, res) => { const { JF_HOST, JF_API_KEY } = req.body; - console.log(req.body); - const { rows } = await db.query('UPDATE app_config SET "JF_HOST"=$1, "JF_API_KEY"=$2', [JF_HOST, JF_API_KEY]); - console.log(`ENDPOINT CALLED: /setconfig: `+rows); - res.send(rows); + const { rows } = await db.query('UPDATE app_config SET "JF_HOST"=$1, "JF_API_KEY"=$2 where "ID"=1', [JF_HOST, JF_API_KEY]); + res.send(rows); + // const { existing } = await db.query('SELECT * FROM app_config where "ID"=1'); + // console.log("Lenght: "+existing.rows[0].length ); + // if(existing != undefined && existing.length != 0) + // { + // console.log("Update Config"); + // const { rows } = await db.query('UPDATE app_config SET "JF_HOST"=$1, "JF_API_KEY"=$2 where "ID"=1', [JF_HOST, JF_API_KEY]); + // res.send(rows); + // } + // else{ + // console.log("Insert Config"); + // const { rows } = await db.query('INSERT into app_config VALUES ( $1, $2, null, null)', [JF_HOST, JF_API_KEY]); + // res.send(rows); + // } + + + + console.log(`ENDPOINT CALLED: /setconfig: `); + }); module.exports = router; diff --git a/src/App.js b/src/App.js index c7cb29c..c164d6e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,17 @@ // import logo from './logo.svg'; import './App.css'; +import React, { useState, useEffect } from 'react'; import { Routes, Route, } from "react-router-dom"; +import Config from './lib/config'; + +import Loading from './pages/components/loading'; + +import Setup from './pages/setup'; + import SideNav from './pages/components/sidenav'; import Home from './pages/home'; @@ -13,9 +20,46 @@ import Activity from './pages/activity'; import UserActivity from './pages/useractivity'; import Libraries from './pages/libraries'; +import RecentlyPlayed from './pages/components/recentlyplayed'; + import UserData from './pages/userdata'; function App() { + + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + + + const fetchConfig = async () => { + try { + const newConfig = await Config(); + setConfig(newConfig); + setLoading(false); + } catch (error) { + if (error.code === 'ERR_NETWORK') { + console.log(error); + } + } + }; + + if (!config) { + fetchConfig(); + } + +}, [config]); + +if (loading) { + return ; +} + +if (!config || config.apiKey ==null) { + return ; +} + + + return (
@@ -28,6 +72,7 @@ function App() { } /> } /> } /> + } />
diff --git a/src/classes/jellyfin-api.js b/src/classes/jellyfin-api.js new file mode 100644 index 0000000..28e6528 --- /dev/null +++ b/src/classes/jellyfin-api.js @@ -0,0 +1,126 @@ +import { Component } from 'react'; +import axios from 'axios'; +import Config from '../lib/config'; + +class API extends Component { + constructor(props) { + super(props); + this.state = { + data: [], + }; + } + + + async getSessions() { + try { + const config = await Config(); + const url = `${config.hostUrl}/Sessions`; + const response = await axios.get(url, { + headers: { + 'X-MediaBrowser-Token': config.apiKey, + }, + }); + return response.data; + } catch (error) { + console.log(error); + return []; + } + } + + async getActivityData(limit) { + if(limit===undefined || limit<1) + { + return[]; + } + try { + const config = await Config(); + const url = `${config.hostUrl}/System/ActivityLog/Entries?limit=${limit}`; + const response = await axios.get(url, { + headers: { + 'X-MediaBrowser-Token': config.apiKey, + }, + }); + return response.data; + } catch (error) { + console.log(error); + return []; + } + } + + async getAdminUser() { + try { + const config = await Config(); + const url = `${config.hostUrl}/Users`; + const response = await axios.get(url, { + headers: { + 'X-MediaBrowser-Token': config.apiKey, + }, + }); + const adminUser = response.data.filter(user => user.Policy.IsAdministrator === true); + return adminUser || null; + } catch (error) { + console.log(error); + return []; + } + } + + async getLibraries() { + + try { + const config = await Config(); + const admins=await this.getAdminUser() + const userid=admins[0].Id; + const url = `${config.hostUrl}/users/${userid}/Items`; + const response = await axios.get(url, { + headers: { + 'X-MediaBrowser-Token': config.apiKey, + }, + }); + const mediafolders = response.data.Items.filter(type => ['tvshows','movies'].includes(type.CollectionType)); + return mediafolders || null; + } catch (error) { + console.log(error); + return []; + } + } + + async getItem(itemID) { + + try { + const config = await Config(); + const admins=await this.getAdminUser() + const userid=admins[0].Id; + const url = `${config.hostUrl}/users/${userid}/Items?ParentID=${itemID}`; + const response = await axios.get(url, { + headers: { + 'X-MediaBrowser-Token': config.apiKey, + }, + }); + return response.data.Items; + } catch (error) { + console.log(error); + return []; + } + } + + async getRecentlyPlayed(userid,limit) { + + try { + const config = await Config(); + const url = `${config.hostUrl}/users/${userid}/Items/Resume?limit=${limit}`; + const response = await axios.get(url, { + headers: { + 'X-MediaBrowser-Token': config.apiKey, + }, + }); + return response.data.Items; + } catch (error) { + console.log(error); + return []; + } + } + + +} + +export default API; diff --git a/src/classes/sync.js b/src/classes/sync.js index ffe49be..a83a33c 100644 --- a/src/classes/sync.js +++ b/src/classes/sync.js @@ -2,7 +2,7 @@ import { Component } from 'react'; import axios from 'axios'; import Config from '../lib/config'; -class GetSeries extends Component { +class sync extends Component { constructor(props) { super(props); this.state = { @@ -11,10 +11,50 @@ class GetSeries extends Component { } - async getData() { + async getAdminUser() { try { const config = await Config(); - const url = `${config.hostUrl}/users/5f63950a2339462196eb8cead70cae7e/Items?ParentID=9d7ad6afe9afa2dab1a2f6e00ad28fa6`; + const url = `${config.hostUrl}/Users`; + const response = await axios.get(url, { + headers: { + 'X-MediaBrowser-Token': config.apiKey, + }, + }); + const adminUser = response.data.filter(user => user.Policy.IsAdministrator === true); + return adminUser || null; + } catch (error) { + console.log(error); + return []; + } + } + + async getLibraries() { + + try { + const config = await Config(); + const admins=await this.getAdminUser() + const userid=admins[0].Id; + const url = `${config.hostUrl}/users/${userid}/Items`; + const response = await axios.get(url, { + headers: { + 'X-MediaBrowser-Token': config.apiKey, + }, + }); + const mediafolders = response.data.Items.filter(type => ['tvshows','movies'].includes(type.CollectionType)); + return mediafolders || null; + } catch (error) { + console.log(error); + return []; + } + } + + async getItem(itemID) { + + try { + const config = await Config(); + const admins=await this.getAdminUser() + const userid=admins[0].Id; + const url = `${config.hostUrl}/users/${userid}/Items?ParentID=${itemID}`; const response = await axios.get(url, { headers: { 'X-MediaBrowser-Token': config.apiKey, @@ -26,6 +66,8 @@ class GetSeries extends Component { return []; } } + + } -export default GetSeries; +export default sync; diff --git a/src/lib/config.js b/src/lib/config.js index 538639a..52ec053 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -2,7 +2,7 @@ import axios from 'axios'; async function Config() { try { - const response = await axios.get('http://10.0.0.99:3003/api/getconfig'); + const response = await axios.get('http://10.0.0.20:3003/api/getconfig'); const { JF_HOST, JF_API_KEY, APP_USER, APP_PASSWORD } = response.data[0]; return { hostUrl: JF_HOST, apiKey: JF_API_KEY, username: APP_USER, password: APP_PASSWORD }; } catch (error) { diff --git a/src/lib/navdata.js b/src/lib/navdata.js index b60cf41..a34330c 100644 --- a/src/lib/navdata.js +++ b/src/lib/navdata.js @@ -5,6 +5,9 @@ import FileListFillIcon from 'remixicon-react/FileListFillIcon'; import BarChartFillIcon from 'remixicon-react/BarChartFillIcon'; import SettingsFillIcon from 'remixicon-react/SettingsFillIcon'; import GalleryFillIcon from 'remixicon-react/GalleryFillIcon'; +import UserFillIcon from 'remixicon-react/UserFillIcon'; + +import ReactjsFillIcon from 'remixicon-react/ReactjsFillIcon'; export const navData = [ { @@ -24,23 +27,30 @@ export const navData = [ icon: , text: "Libraries", link: "libraries" - }, + } , { id: 3, + icon: , + text: "Recently Played", + link: "recent" + }, + { + id: 4, icon: , text: "User Activity", link: "usersactivity" }, { - id: 4, - icon: , - text: "User Data", + id: 5, + icon: , + text: "Library Data", link: "userdata" }, { - id: 5, + id: 6, icon: , text: "Settings", link: "settings" } + ] \ No newline at end of file diff --git a/src/models/libraryItem.js b/src/models/libraryItem.js new file mode 100644 index 0000000..63c9962 --- /dev/null +++ b/src/models/libraryItem.js @@ -0,0 +1,7 @@ +class libraryItem { + constructor(id, name, email) { + this.id = id; + this.name = name; + this.email = email; + } + } \ No newline at end of file diff --git a/src/pages/activity.js b/src/pages/activity.js index 179d545..f1ac6f5 100644 --- a/src/pages/activity.js +++ b/src/pages/activity.js @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; -import axios from 'axios'; -import Config from '../lib/config'; +import API from '../classes/jellyfin-api'; import '../App.css' @@ -10,55 +9,30 @@ import Loading from './components/loading'; function Activity() { const [data, setData] = useState([]); - const [config, setConfig] = useState(null); useEffect(() => { - - - const fetchConfig = async () => { - try { - const newConfig = await Config(); - setConfig(newConfig); - } catch (error) { - if (error.code === 'ERR_NETWORK') { - console.log(error); - } - } - }; + let _api= new API() const fetchData = () => { - if (config) { - const url = `${config.hostUrl}/System/ActivityLog/Entries?limit=30`; - const apiKey = config.apiKey; - - axios.get(url, { - headers: { - 'X-MediaBrowser-Token': apiKey, - }, - }) - .then(newData => { - if (data && data.length > 0) { - const newDataOnly = newData.data.Items.filter(item => { - return !data.some(existingItem => existingItem.Id === item.Id); - }); - setData([...newDataOnly, ...data.slice(0, data.length - newDataOnly.length)]); - } else { - setData(newData.data.Items); - } - }) - .catch(error => { - console.log(error); + _api.getActivityData(30).then((ActivityData) => { + if (data && data.length > 0) + { + const newDataOnly = ActivityData.Items.filter(item => { + return !data.some(existingItem => existingItem.Id === item.Id); }); - } + setData([...newDataOnly, ...data.slice(0, data.length - newDataOnly.length)]); + } else + { + setData(ActivityData.Items); + } + }); }; - if (!config) { - fetchConfig(); - } - const intervalId = setInterval(fetchData, 2000); + const intervalId = setInterval(fetchData, 1000); return () => clearInterval(intervalId); - }, [data,config]); + }, [data]); + @@ -70,7 +44,7 @@ function Activity() { hour: 'numeric', minute: 'numeric', second: 'numeric', - hour12: true + hour12: false }; if (!data || data.length === 0) { diff --git a/src/pages/components/recent-card.js b/src/pages/components/recent-card.js new file mode 100644 index 0000000..447ec21 --- /dev/null +++ b/src/pages/components/recent-card.js @@ -0,0 +1,69 @@ +import React from 'react'; + +function getLastPlayedTimeString(datetime) { + const now = new Date(); + const lastPlayed = new Date(datetime); + + const timeDifference = Math.abs(now.getTime() - lastPlayed.getTime()); + const yearsDifference = Math.floor(timeDifference / (1000 * 3600 * 24 * 365)); + const weeksDifference = Math.floor( + (timeDifference % (1000 * 3600 * 24 * 365)) / (1000 * 3600 * 24 * 7) + ); + const daysDifference = Math.floor( + (timeDifference % (1000 * 3600 * 24 * 7)) / (1000 * 3600 * 24) + ); + const hoursDifference = Math.floor( + (timeDifference % (1000 * 3600 * 24)) / (1000 * 3600) + ); + const minutesDifference = Math.floor( + (timeDifference % (1000 * 3600)) / (1000 * 60) + ); + + const timeUnits = [ + { label: "year", pluralLabel: "years", value: yearsDifference }, + { label: "week", pluralLabel: "weeks", value: weeksDifference }, + { label: "day", pluralLabel: "days", value: daysDifference }, + { label: "hour", pluralLabel: "hours", value: hoursDifference }, + { label: "minute", pluralLabel: "minutes", value: minutesDifference }, + ]; + + const timeString = timeUnits + .filter((unit) => unit.value > 0) + .map((unit, index, array) => { + const label = unit.value === 1 ? unit.label : unit.pluralLabel; + if (index === array.length - 1 && array.length > 1) { + // Special case for last time unit + return `and ${unit.value} ${label}`; + } else { + return `${unit.value} ${label}`; + } + }) + .join(" "); + + return `Watched ${timeString} ago`; + } + + + +function RecentCard(props) { + + + return ( +
+ + +
+
+ +
+
{props.data.recent.Name}
+
{getLastPlayedTimeString(props.data.recent.UserData.LastPlayedDate)}
+
+ +
+ ); +} + +export default RecentCard; \ No newline at end of file diff --git a/src/pages/components/recentlyplayed.js b/src/pages/components/recentlyplayed.js new file mode 100644 index 0000000..f851e65 --- /dev/null +++ b/src/pages/components/recentlyplayed.js @@ -0,0 +1,68 @@ +import React, { useState, useEffect } from 'react'; +// import axios from 'axios'; +import Config from '../../lib/config'; +import API from '../../classes/jellyfin-api'; + + +import "../css/recent.css" +// import "../../App.css" + + +import RecentCard from './recent-card'; + + +import Loading from './loading'; + + + + + +function RecentlyPlayed() { + const [data, setData] = useState([]); + const [base_url, setURL] = useState(''); + // const [errorHandler, seterrorHandler] = useState({ error_count: 0, error_message: '' }) + + + useEffect(() => { + const _api = new API(); + const fetchData = () => { + _api.getRecentlyPlayed('5f63950a2339462196eb8cead70cae7e',10).then((recentData) => { + setData(recentData); + }); + }; + + + Config().then(config => { + setURL(config.hostUrl); + }).catch(error => { + console.log(error); + } + ); + + const intervalId = setInterval(fetchData, 1000); + return () => clearInterval(intervalId); + }, []); + + + + if (!data || data.length === 0) { + return ; + } + + + return ( + +
+ {data && + data.sort((a, b) => b.UserData.LastPlayedDate.localeCompare(a.UserData.LastPlayedDate)).map(recent => ( + + + + ))} +
+ + ); +} + +export default RecentlyPlayed; + diff --git a/src/pages/components/sessions.js b/src/pages/components/sessions.js index 30af2e0..4c740be 100644 --- a/src/pages/components/sessions.js +++ b/src/pages/components/sessions.js @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; -import axios from 'axios'; +// import axios from 'axios'; import Config from '../../lib/config'; +import API from '../../classes/jellyfin-api'; import "../css/sessions.css" // import "../../App.css" @@ -22,45 +23,23 @@ function Sessions() { useEffect(() => { + const _api = new API(); + const fetchData = () => { + _api.getSessions().then((SessionData) => { + setData(SessionData); + }); + }; + + Config().then(config => { - console.log('hit api'); - // let error_counter=0; setURL(config.hostUrl); - const url = `${config.hostUrl}/sessions`; - const fetchData = () => { - axios.get(url, { - headers: { - 'X-MediaBrowser-Token': config.apiKey, - }, - }) - .then(response => setData(response.data)) - .catch(error => { - console.log(error); - // error_counter++; - // console.log(error_counter); - // if(error_counter>9) - // { - // console.log('Terminating'); - // return () => clearInterval(intervalId); - // } - } - ); - }; - - // fetchData(); - const intervalId = setInterval(fetchData, 1000); - return () => clearInterval(intervalId); }).catch(error => { - // if (error.code === 'ERR_NETWORK') { - // console.log(errorHandler.error_count); - // let _error_count = 1; - // let _error_message = ''; - // seterrorHandler({ error_count: _error_count, error_message: _error_message }); - // } - console.log(error); } ); + + const intervalId = setInterval(fetchData, 1000); + return () => clearInterval(intervalId); }, []); @@ -76,8 +55,8 @@ function Sessions() { {data && data.sort((a, b) => a.Id.padStart(12, '0').localeCompare(b.Id.padStart(12, '0'))).map(session => ( - - + + ))} diff --git a/src/pages/css/recent.css b/src/pages/css/recent.css new file mode 100644 index 0000000..af16c7e --- /dev/null +++ b/src/pages/css/recent.css @@ -0,0 +1,75 @@ +.recent { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(185px, 200px)); + grid-auto-rows: 340px;/* max-width+offset so 215 + 20*/ + background-color: rgba(0,0,0,0.5); + padding: 20px; + border-radius: 4px; + margin-right: 20px; + color: white; + + +} + + + +.recent-card +{ + display: flex; + flex-direction: column; + + /* background-color: grey; */ + box-shadow: 0 0 20px rgba(255, 255, 255, 0.05); + + height: 320px; + width: 185px; + border-radius: 4px; + + + /* Add a third row that takes up remaining space */ +} + + +.card-banner { + width: 100%; + height: 70%; + background-size: cover; + background-repeat: no-repeat; + background-position: center top; + border-radius: 4px 4px 0px 0px; + +} + +.recent-card-details { + + + width: 100%; + height: 30%; + position: relative; + /* margin: 4px; */ + + /* background-color: #f71b1b; */ +} + +.recent-card-item-name { + width: 185px; + overflow: hidden; + text-overflow: ellipsis; + position: absolute; + margin: 0; + + +/* + + position: absolute; */ +} + +.recent-card-last-played{ + width: 185px; + overflow: hidden; + text-overflow: ellipsis; + position: absolute; + bottom: 0; + margin: 0; +} + diff --git a/src/pages/css/sessions.css b/src/pages/css/sessions.css index b23b30e..da342cc 100644 --- a/src/pages/css/sessions.css +++ b/src/pages/css/sessions.css @@ -15,6 +15,7 @@ color: white; background-color: grey; + box-shadow: 0 0 20px rgba(255, 255, 255, 0.05); max-height: 215px; max-width: 500px; diff --git a/src/pages/css/setup.css b/src/pages/css/setup.css new file mode 100644 index 0000000..f2255d5 --- /dev/null +++ b/src/pages/css/setup.css @@ -0,0 +1,117 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap'); +/* *{ + margin: 0; + padding: 0; + font-family: 'poppins',sans-serif; +} */ + + +section{ + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + width: 100%; + + /* background: url('background6.jpg')no-repeat; */ + background-position: center; + background-size: cover; +} +.form-box{ + position: relative; + width: 400px; + height: 450px; + background: transparent; + border: 2px solid rgba(255,255,255,0.5); + border-radius: 20px; + backdrop-filter: blur(15px); + display: flex; + justify-content: center; + align-items: center; + +} +h2{ + font-size: 2em; + color: #fff; + text-align: center; +} +.inputbox{ + position: relative; + margin: 30px 0; + width: 310px; + border-bottom: 2px solid #fff; +} +.inputbox label{ + position: absolute; + top: 50%; + left: 5px; + transform: translateY(-50%); + color: #fff; + font-size: 1em; + pointer-events: none; + transition: .2s; +} +input:focus ~ label, +input:valid ~ label{ +top: -15px; +} +.inputbox input { + width: 100%; + height: 50px; + background: transparent; + border: none; + outline: none; + font-size: 1em; + color: #fff; +} +.inputbox ion-icon{ + position: absolute; + right: 8px; + color: #fff; + font-size: 1.2em; + top: 20px; +} +.forget{ + margin: -15px 0 15px ; + font-size: .9em; + color: #fff; + display: flex; + justify-content: space-between; +} + +.forget label input{ + margin-right: 3px; + +} +.forget label a{ + color: #fff; + text-decoration: none; +} +.forget label a:hover{ + text-decoration: underline; +} +.setup-button{ + width: 100%; + height: 40px; + border-radius: 40px; + background: #fff; + border: none; + outline: none; + cursor: pointer; + font-size: 1em; + font-weight: 600; +} +.register{ + font-size: .9em; + color: #fff; + text-align: center; + margin: 25px 0 10px; +} +.register p a{ + text-decoration: none; + color: #fff; + font-weight: 600; +} +.register p a:hover{ + text-decoration: underline; +} \ No newline at end of file diff --git a/src/pages/setup.js b/src/pages/setup.js new file mode 100644 index 0000000..3bd3df6 --- /dev/null +++ b/src/pages/setup.js @@ -0,0 +1,160 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import Config from '../lib/config'; + +import './css/setup.css' + +// import Loading from './components/loading'; + +function Setup() { + const [config, setConfig] = useState(null); + const [formValues, setFormValues] = useState({}); + const [processing, setProcessing] = useState(false); + const [submitButtonText, setsubmitButtonText] = useState('Save'); + + function handleFormChange(event) { + + setFormValues({ ...formValues, [event.target.name]: event.target.value }); + } + + async function validateSettings(_url, _apikey) { + // Send a GET request to /system/configuration to test copnnection + let isValid = false; + let errorMessage = ''; + await axios.get(_url + '/system/configuration', { + headers: { + 'X-MediaBrowser-Token': _apikey, + }, + }) + .then(response => { + // console.log('HTTP status code:', response.status); // logs the HTTP status code + //console.log('Response data:', response.data); // logs the response data + + if (response.status === 200) { + isValid = true; + } + + + }) + .catch(error => { + // console.log(error.code); + if (error.code === 'ERR_NETWORK') { + isValid = false; + errorMessage = `Unable to connect to Jellyfin Server`; + } else + if (error.response.status === 401) { + isValid = false; + errorMessage = `Error ${error.response.status} Not Authorized`; + } else + if (error.response.status === 404) { + isValid = false; + errorMessage = `Error ${error.response.status}: The requested URL was not found.`; + } else { + isValid = false; + errorMessage = `Error : ${error.response.status}`; + } + + }); + + return ({ isValid: isValid, errorMessage: errorMessage }); + } + + async function handleFormSubmit(event) { + setProcessing(true); + event.preventDefault(); + + + // if(formValues.JF_HOST=='' || formValues.JF_API_KEY=='') + // { + // setsubmitButtonText('Plea'); + // return; + // } + + + let validation = await validateSettings(formValues.JF_HOST, formValues.JF_API_KEY); + + + if (!validation.isValid) { + + setsubmitButtonText(validation.errorMessage); + setProcessing(false); + return; + + } + + + // Send a POST request to /api/setconfig/ with the updated configuration + axios.post('http://localhost:3003/api/setconfig/', formValues, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => { + setsubmitButtonText('Settings Saved'); + setProcessing(false); + setTimeout(() => { + window.location.href = '/'; + }, 1000); + + return; + }) + .catch(error => { + setsubmitButtonText('Error Saving Settings'); + setProcessing(false); + + }); + + + + } + + + useEffect(() => { + + + const fetchConfig = async () => { + try { + const newConfig = await Config(); + setConfig(newConfig); + } catch (error) { + if (error.code === 'ERR_NETWORK') { + console.log(error); + } + } + }; + + if (!config) { + fetchConfig(); + } + + }, [config]); + + + + return ( +
+ +
+ + +
+

Setup

+
+ + + +
+
+ + +
+ + +
+ +
+
+ ); +} + +export default Setup; diff --git a/src/pages/userdata.js b/src/pages/userdata.js index 4864a59..64c4109 100644 --- a/src/pages/userdata.js +++ b/src/pages/userdata.js @@ -1,16 +1,22 @@ import React, { useState, useEffect } from 'react'; -import GetSeries from '../classes/sync'; +// import sync from '../classes/sync'; import './css/libraries.css'; import Loading from './components/loading'; +import API from '../classes/jellyfin-api'; + function UserData() { const [data, setData] = useState([]); useEffect(() => { - const seriesInstance = new GetSeries(); // create an instance of the GetSeries class - seriesInstance.getData().then((seriesData) => { + const seriesInstance = new API(); // create an instance of the GetSeries class + seriesInstance.getLibraries().then((seriesData) => { setData(seriesData); }); + + + + }, []); // run this effect only once, when the component mounts if (!data || data.length === 0) {