/**
* @file SettingsPage.js
* @description This file contains the settings page of the application
* @module SettingsPage
*/
import React, { useEffect, useState } from 'react';
import { useNavigate } from "react-router-dom"
import ProfileImage from './assets/avatar.png';
import GoogleImage from './assets/google.png';
import SpotifyImage from './assets/spotify.png';
import TwitterImage from './assets/twitter.png';
import TwitchImage from './assets/twitch.png';
import StravaImage from './assets/strava.png';
import LocationImage from './assets/locate.png';
import DeconnexionImage from './assets/deconnexion.png';
import ArrowRight from './assets/arrowRight.png';
import { addDataIntoCache, getDataFromCache } from './Common/CacheManagement'
import { authWithCache } from './Common/Login';
import { useLocation } from 'react-router-dom';
import { auth } from './firebaseConfig';
const querystring = require('querystring-es3');
/**
* @description Styles of the page
* @constant {Object} styles - Styles of the page
*/
const styles = {
profile: {
position: 'relative',
backgroundColor: '#5281B7',
width: "20%",
height: 100,
left: "40%",
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
borderRadius: 15,
marginBottom: 10
},
profilePicture: {
position: 'relative',
width: 80,
height: 80,
borderRadius: 100,
left: "5%",
},
profileEmail: {
position: 'relative',
},
profileRectEmail: {
position: 'relative',
paddingLeft: 10,
paddingRight: 10,
backgroundColor: '#ffffff',
borderRadius: 5,
},
location: {
position: 'relative',
backgroundColor: '#5281B7',
width: "20%",
height: 50,
left: "40%",
display: 'flex',
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-around',
borderRadius: 15,
marginBottom: 10
},
locationText: {
position: 'relative',
},
locationImage: {
position: 'relative',
width: 30,
height: 30,
},
connexionServices: {
position: 'relative',
width: "20%",
left: "40%",
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-around',
borderRadius: 15,
marginBottom: 10
},
service: {
position: 'relative',
backgroundColor: '#5281B7',
height: 50,
width: "100%",
borderRadius: 15,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
marginBottom: 10
},
serviceImage: {
position: 'relative',
width: 30,
height: 30,
},
serviceArrow: {
position: 'relative',
width: 15,
height: 30,
},
deconnexion: {
position: 'relative',
backgroundColor: '#5281B7',
height: 50,
width: "20%",
left: "40%",
borderRadius: 15,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
marginBottom: 10,
marinTop: 10
},
header: {
position: 'relative',
height: 50,
width: "100%",
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
borderRadius: 15,
marginBottom: 10,
},
headerTitle: {
position: 'relative',
color: '#000000',
fontSize: 32,
fontWeight: 'bold',
},
headerBackButton: {
position: 'relative',
width: 15,
height: 30,
transform: 'rotate(180deg)',
paddingLeft: 10,
},
subHeader: {
position: 'relative',
height: 50,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
headerHomeText: {
position: 'relative',
}
}
/**
* @description Settings page of the application
* @function SettingsPage - The settings page
* @param {Object} props contains the user information and allAreas
* @returns The html page
*/
export default function SettingsPage(props) {
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
try {
authWithCache(props.setUserInformation, props);
console.log("Already logged in")
} catch (error) {
console.log("Unable to login" + error);
navigate("/auth")
}
}, [])
useEffect(() => {
updateIP({target:{value: props.userInformation.ip}})
}, [props.userInformation.id])
/**
* It returns a div with a profile picture and an email address
* @function Profile - The profile div
* @returns A div with a profile picture and the email of the user.
*/
function Profile() {
return (
<div style={styles.profile}>
<img src={ProfileImage} style={styles.profilePicture}></img>
<div style={styles.profileRectEmail}>
<div style={styles.profileEmail}>{props.userInformation.mail}</div>
</div>
</div>
)
}
/**
* Generates a random string containing numbers and letters
* @function generateRandomString
* @param {number} length The length of the string
* @return {string} The generated string
*/
var generateRandomString = function (length) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
/**
* It returns a div with an image and a text element
* @function Location - The location div
* @returns A div with a location image and a text that says the city of the
* user.
*/
function Location() {
return (
<div style={styles.location}>
<img src={LocationImage} style={styles.locationImage}></img>
<p style={styles.locationText}>{(props.userInformation.coord.city == null) ? props.userInformation.coord.city : "UNDEFINED"}</p>
</div>
)
}
/**
* Ensure the log in of the user on Spotify
* @function spotifyConnexion
* @async
*/
async function spotifyConnexion() {
const scopes = [
'ugc-image-upload',
'user-read-playback-state',
'user-modify-playback-state',
'user-read-currently-playing',
'streaming',
'app-remote-control',
'user-read-email',
'user-read-private',
'playlist-read-collaborative',
'playlist-modify-public',
'playlist-read-private',
'playlist-modify-private',
'user-library-modify',
'user-library-read',
'user-top-read',
'user-read-playback-position',
'user-read-recently-played',
'user-follow-read',
'user-follow-modify'
].join(' ');
try {
await fetch(props.userInformation.ip + "/spotify").then(response => {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({uid: props.userInformation.id})
}
const uid = props.userInformation.id
fetch(props.userInformation.ip + "/spotify/post/", requestOptions)
.then(response => {
response.json().then(data => {
})
})
response.json().then(data => {
var clientID = data
const url = 'https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: clientID,
scope: scopes,
show_dialog: true,
redirect_uri: 'http://localhost:8080/spotify/callback',
state: generateRandomString(16)
})
console.log(url)
window.open(url, 'popup', 'width=600,height=800')
})
}).catch(error => {
console.log(error)
})
} catch (error) {
console.log(error);
}
}
/**
* It returns a div with an image, a text and another image
* @function Service - The service div
* @param props - the props object
* @returns A div with an image, a text and an arrow.
*/
function Service(props) {
return (
<div id={props.service} style={styles.service} onMouseOver={updateCursor} onMouseOut={updateCursor} onClick={props.onPress}>
<img src={props.image} style={styles.serviceImage}></img>
<p style={styles.serviceText}>Connexion à {props.service}</p>
<img src={ArrowRight} style={styles.serviceArrow}></img>
</div>
)
}
/**
* Authenticates the user with Strava API.
* @async
* @function stravaConnection
*/
async function stravaConnection() {
console.log('strava connection');
console.log(props);
await fetch(props.userInformation.ip + '/strava/auth/' + props.userInformation.id, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then((response) => {
response.json().then(async (data) => {
window.open(data, 'popup', 'width=600,height=800')
});
});
}
/**
* Authenticates the user with Twitch API.
* @function twitchConnexion
*/
function twitchConnexion() {
const scopes = [
"analytics:read:extensions",
"analytics:read:games",
"moderator:read:followers",
"channel:manage:moderators",
"channel:manage:predictions",
"channel:manage:polls",
"user:manage:whispers"
].join(" ");
const twitch_oauth_url = "https://id.twitch.tv/oauth2/authorize"
const response_type = "token"
twitchAuth(scopes, twitch_oauth_url, response_type)
}
/**
* Encode Uri
* @function encodeQueryString
* @param {*} params contains the elements to be added to the url.
* @returns a string separated by an "&".
*/
function encodeUrlScope(params)
{
let items = []
for (let key in params) {
let value = encodeURIComponent(params[key])
items.push(`${key}=${value}`)
}
return items.join("&")
}
/**
* Authenticates the user with Twitch OAuth and send an access token to the back.
* @async
* @function twitchAuth
* @param {string} scopes - The list of scopes to be authorized by the user.
* @param {string} twitch_oauth_url - The URL for the Twitch OAuth endpoint.
* @param {string} response_type - The response type for the authorization request.
*/
async function twitchAuth(scopes, twitch_oauth_url, response_type) {
var url = "";
try {
await fetch(props.userInformation.ip + "/twitch/get").then(response => {
response.json().then(async data => {
const params = {
client_id: data.clientId,
redirect_uri: data.redirect_url,
scope : scopes,
response_type: response_type
}
url = `${twitch_oauth_url}?${encodeUrlScope(params)}`
window.open(url, 'popup', 'width=600,height=800')
// await Linking.openURL(url).catch((err) => console.log('An error occurred', err))
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({uid: props.userInformation.id})
}
fetch(props.userInformation.ip + "/twitch/post/", requestOptions)
.then(response => {
response.json().then(data => {
})
})
})
}).catch(error => {
console.log(error)
})
} catch (error) {
console.log(error);
}
}
/**
* It returns a div with a style of connexionServices, which contains 5
* Service components
* @function ServicesAuth - The services auth div
* @returns A div with the className connexionServices and a list of Service
* components.
*/
function ServicesAuth() {
return (
<div style={styles.connexionServices}>
<Service image={GoogleImage} service="Google"onPress={googleConnexion}/>
<Service image={SpotifyImage} service="Spotify" onPress={spotifyConnexion} />
<Service image={TwitterImage} service="Twitter" onPress={twitterConnexion}/>
<Service image={TwitchImage} service="Twitch" onPress={twitchConnexion} />
<Service image={StravaImage} service="Strava" onPress={stravaConnection}/>
</div>
)
}
/**
* Empty for the moment
* @function stravaConnexion
*/
function stravaConnexion() {
}
/**
* Empty for the moment
* @function twitterConnexion
* @async
*/
async function twitterConnexion() {
try {
await fetch(props.userInformation.ip + "/twitter/get").then(response => {
response.json().then(async data => {
const params = {
consumerKey: data.appKey,
consumerSecret: data.appSecret,
callbackUrl: 'http://localhost:8080/twitter/sign',
uid: props.userInformation.id
}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({params: params})
}
await fetch(props.userInformation.ip + "/twitter/login/", requestOptions)
.then(response => {
response.json().then(async data => {
if (data) {
window.open(data.body, 'popup', 'width=600,height=800')
}
})
})
})
}).catch(error => {
console.log(error)
})
} catch (error) {
console.log(error);
}
}
/**
* Empty for the moment
* @function googleConnexion
*/
function googleConnexion() {
}
/**
* It returns a div with a clickable image and a text
* @function Deconnexion - The deconnexion div
* @returns A deconnexion button
*/
function Deconnexion() {
return (
<div style={styles.deconnexion} onClick={() => { auth.signOut().then(() => {console.log('disconnected')}) ; addDataIntoCache("area", {}); navigate('/auth') }} onMouseOver={updateCursor} onMouseOut={updateCursor}>
<img src={DeconnexionImage}></img>
<p>Deconnexion</p>
<p></p>
</div>
)
}
/**
* The function goHome() is called when the user clicks on the "Home" button
* @function goHome - The function that is called when the user clicks on the
* "Home" button
*/
function goHome() {
navigate('/home');
}
/**
* If the cursor is a pointer, make it default. If the cursor is default, make
* it a pointer.
* @function updateCursor - The function that is called when the user hovers
* over a div
*/
function updateCursor() {
var cursor = document.getElementById('global');
if (cursor.style.cursor === 'pointer') {
cursor.style.cursor = 'default';
} else {
cursor.style.cursor = 'pointer';
}
}
/**
* This function returns a div with a Google image, a title, and a blank div
* @function Header - The header div
* @returns A div with a header image, a header title, and a div.
*/
function Header() {
return (
<div style={styles.header}>
<div style={styles.subHeader} onClick={goHome} onMouseOver={updateCursor} onMouseOut={updateCursor}>
<img src={ArrowRight} style={styles.headerBackButton}></img>
<p style={styles.headerHomeText}>Home</p>
</div>
<div style={styles.headerTitle}>Settings</div>
<div></div>
</div>
)
}
const [color, setColor] = useState('black')
/**
* It fetches a resource, but if the fetch takes longer than the timeout, it
* aborts the fetch
* @function fetchWithTimeout
* @param {string} resource - The URL to fetch.
* @param {*} [options] - An object containing any custom settings that you want
* to apply to the request.
* @returns A function that takes two parameters, resource and options.
*/
async function fetchWithTimeout(resource, options = {}) {
const { timeout = 8000 } = options;
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const response = await fetch(resource, {
...options,
signal: controller.signal
});
clearTimeout(id);
return response;
}
/**
* It updates the IP address of the user in the state of the application
* @function updateIP
* @param event - the event that triggered the function
*/
function updateIP(event) {
setColor("black")
console.log(event.target.value)
try {
fetchWithTimeout(event.target.value + "/testConnexion", { timeout: 500 }).then(response => {
if (response.status == 200) {
setColor("#5281B7")
} else {
setColor("red")
}
console.log(response)
}).catch(error => {
setColor("red")
console.log(error)
})
} catch (error) {
setColor("red")
console.log(error)
}
props.setUserInformation({
mail: props.userInformation.mail,
locationAccept: props.userInformation.locationAccept,
coord: {
latitude: props.userInformation.coord.latitude,
longitude: props.userInformation.coord.longitude,
city: props.userInformation.coord.city
},
id: props.userInformation.id,
services: {
spotifyId: props.userInformation.spotifyId,
googleId: props.userInformation.googleId,
twitterId: props.userInformation.twitterId,
twitchId: props.userInformation.twitchId,
stravaId: props.userInformation.stravaId
},
ip: event.target.value
})
}
const ipStyle = {
ip: {
position: 'relative',
backgroundColor: color,
width: "20%",
height: 50,
left: "40%",
display: 'flex',
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-around',
borderRadius: 15,
marginBottom: 10
},
}
return (
<div id='global' style={{ textAlign: 'center' }}>
<Header />
<Profile />
<div style={ipStyle.ip}>
<div>
{props.userInformation.ip}
</div>
</div>
<Location />
<ServicesAuth />
<Deconnexion />
</div>
)
}