Issue #37 y #36 Agregar a la lista

This commit is contained in:
Daniel Cortes
2020-07-12 16:06:38 -04:00
parent ed1c25a41f
commit 61bd20ea27
15 changed files with 285 additions and 11 deletions

24
package-lock.json generated
View File

@@ -5461,6 +5461,11 @@
"strip-eof": "^1.0.0" "strip-eof": "^1.0.0"
} }
}, },
"exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
},
"exit": { "exit": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
@@ -11347,6 +11352,17 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
}, },
"react-modal": {
"version": "3.11.2",
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz",
"integrity": "sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w==",
"requires": {
"exenv": "^1.2.0",
"prop-types": "^15.5.10",
"react-lifecycles-compat": "^3.0.0",
"warning": "^4.0.3"
}
},
"react-router": { "react-router": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
@@ -13835,6 +13851,14 @@
"makeerror": "1.0.x" "makeerror": "1.0.x"
} }
}, },
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"requires": {
"loose-envify": "^1.0.0"
}
},
"watchpack": { "watchpack": {
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",

View File

@@ -18,6 +18,7 @@
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-icons": "^3.10.0", "react-icons": "^3.10.0",
"react-json-view": "^1.19.1", "react-json-view": "^1.19.1",
"react-modal": "^3.11.2",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",
"react-tabs": "^3.1.1", "react-tabs": "^3.1.1",

View File

@@ -1,7 +1,160 @@
import React from "react"; import React, {useState} from "react";
import Modal from 'react-modal';
import {Button} from './Button'; import {Button} from './Button';
import './AddToList.scss'; import './AddToList.scss';
import {useStateValue} from "../services/State";
import {wait} from "../services/utils";
import {AiFillStar, AiOutlineStar} from "react-icons/all";
import {add_to_list} from "../services/list_service";
const Star = (props) => {
return (
<div onClick={props.onClick}>
{props.selected ?
<AiFillStar className={'star selected'}/> :
<AiOutlineStar className={'star'}/>
}
</div>
)
}
const Stars = (props) => {
const getSelected = (i) => {
return i <= props.stars
}
const handleStarClick = (i) => () => {
if (props.stars === i) {
props.onChange(0);
} else {
props.onChange(i);
}
}
const stars = [
<Star key={1} onClick={handleStarClick(1)} selected={getSelected(1)}/>,
<Star key={2} onClick={handleStarClick(2)} selected={getSelected(2)}/>,
<Star key={3} onClick={handleStarClick(3)} selected={getSelected(3)}/>,
<Star key={4} onClick={handleStarClick(4)} selected={getSelected(4)}/>,
<Star key={5} onClick={handleStarClick(5)} selected={getSelected(5)}/>,
]
return (
<div className={'stars'}>
{stars}
</div>
);
}
const Opinion = (props) => {
const handleInput = (event) => {
props.onChange(event.target.value);
}
return (
<textarea onChange={handleInput}/>
);
}
const AddForm = (props) => {
const context = useStateValue()[0];
const [state, setState] = useState(0);
const [stars, setStars] = useState(0);
const [opinion, setOpinion] = useState('');
const [error, setError] = useState(null);
const type = props.type;
const handleClose = props.onClose;
let title;
switch (type) {
case 'artist':
title = 'Comparte lo que te gusta de este artista';
break;
case 'release':
title = 'Comparte lo que te gusta de este lanzamiento';
break;
case 'disc':
title = 'Comparte lo que te gusta de este disco';
break;
case 'song':
title = 'Comparte lo que te gusta de esta canción';
break;
default:
title = 'Comparte lo que te gusta de esto';
}
const handleSave = () => {
add_to_list(context.user.access_token, JSON.parse(window.localStorage.getItem('user')).id,
props.entity, props.type, []).then(() => {
setState(1);
}).catch((error) => {
console.log('Request error', error.response)
if(error.response.data.error){
setState(2);
setError(error.response.data.error);
}
})
}
const errorComponent = (<h3>{error}</h3>)
const doneComponent = (
<h3>Agregado!</h3>
)
const formComponent = (
<div className={'add-form'}>
<h3>{title}</h3>
<div className="form">
<div className="form-group">
<label>Clasificacion</label>
<Stars stars={stars} onChange={setStars}/>
</div>
<div className="form-group">
<label>Opinion</label>
<Opinion opinion={opinion} onChange={setOpinion}/>
</div>
</div>
<div className={'buttons'}>
<Button className='primary' onClick={handleSave} text={'Agregar'}/>
<Button onClick={handleClose} text={'Cerrar'}/>
</div>
</div>
);
if(state === 0){
return formComponent
} else if (state === 1) {
return doneComponent;
} else if (state === 2) {
return errorComponent;
}
}
export const AddToList = (props) => { export const AddToList = (props) => {
return <Button className='add-to-list' text='Agregar a mi lista'/> const context = useStateValue()[0];
const [show, setShow] = useState(false);
const handleOpenModal = () => setShow(true);
const handleCloseModal = () => setShow(false);
Modal.setAppElement('#root');
let modalChildren = <AddForm type={props.type} entity={props.entity} onClose={handleCloseModal}/>
if (!context.user.auth) {
modalChildren = <h3>Tienes que tener una cuenta para poder agregar a tu lista</h3>
}
return (
<div>
<Button className='add-to-list' onClick={handleOpenModal} text='Agregar a mi lista'/>
<Modal className='modal' onRequestClose={handleCloseModal} isOpen={show}>
{modalChildren}
</Modal>
</div>
)
} }

View File

@@ -1,4 +1,61 @@
.add-to-list { .modal {
position: absolute;
left: 50%;
top: 25%;
transform: translate(-50%, -25%);
max-width: 75em;
width: 90%;
margin: auto;
padding: 2em;
background-color: var(--white);
outline: var(--accent) var(--line-width) solid;
}
.stars {
display: flex;
justify-content: flex-start;
.star {
margin: inherit;
width: 1.5em;
height: 1.5em;
cursor: pointer;
}
.star.selected {
color: var(--accent);
}
}
.add-form {
.form {
& > * {
margin-bottom: 1em;
}
textarea {
max-width: 100%;
width: 100%;
height: 10ch;
}
label {
display: block;
margin-top: 1em;
}
}
.buttons .button {
margin-right: 1em;
}
}
.button.add-to-list {
--color: var(--accent); --color: var(--accent);
--line-width: 3px; --line-width: 3px;
font-size: 1.2em; font-size: 1.2em;

View File

@@ -5,6 +5,7 @@ export const Button = (props) => {
const className = props.className; const className = props.className;
const text = props.text; const text = props.text;
const type = props.type ? props.type : 'button' const type = props.type ? props.type : 'button'
const handleClick = props.onClick;
return <button className={`${type} ${className}`}>{text}</button> return <button className={`${type} ${className}`} onClick={handleClick}>{text}</button>
} }

View File

@@ -31,6 +31,9 @@
&:active { --color: var(--accent) } &:active { --color: var(--accent) }
} }
.button.primary {
--color: var(--accent)
}
.link { .link {
display: inline; display: inline;

View File

@@ -22,7 +22,7 @@ export const Entity = (props) => {
</ul> </ul>
} }
</div> </div>
<AddToList/> <AddToList type={props.type} entity={props.entity}/>
</div> </div>
{hasCover && {hasCover &&

View File

@@ -40,7 +40,7 @@ const SocialNetwork = (props) => {
} }
return ( return (
<a href={link} target={'_blank'} className={'social_network'}>{icon}</a> <a href={link} rel='noopener noreferrer' target={'_blank'} className={'social_network'}>{icon}</a>
) )
} }

View File

@@ -1,4 +1,5 @@
import axios from "axios"; import axios from "axios";
import {getUser} from "./user_service";
const current_host = `${window.location.protocol}//${window.location.host}` const current_host = `${window.location.protocol}//${window.location.host}`
const oauth_url = `${process.env.REACT_APP_API_SERVER}/oauth`; const oauth_url = `${process.env.REACT_APP_API_SERVER}/oauth`;
@@ -99,14 +100,19 @@ export const auth = async (params) => {
// en localstorage para ser utilizado mas tarde para renovar el access_token // en localstorage para ser utilizado mas tarde para renovar el access_token
window.localStorage.clear() window.localStorage.clear()
const access_token = response.access_token;
const refresh = response.refresh_token; const refresh = response.refresh_token;
const expires = new Date(new Date().getTime() + ((response.expires_in) * 1000)) const expires = new Date(new Date().getTime() + ((response.expires_in) * 1000))
window.localStorage.setItem('refresh_token', refresh); window.localStorage.setItem('refresh_token', refresh);
window.localStorage.setItem('expires', expires); window.localStorage.setItem('expires', expires);
// Almacenar el usuario en localStorage
const user = await getUser(access_token);
window.localStorage.setItem('user', JSON.stringify(user))
// Finalmente se retorna el access_token para ser utilizado en el estado de la app // Finalmente se retorna el access_token para ser utilizado en el estado de la app
return {status: 'done', access_token: response.access_token}; return {status: 'done', access_token: access_token};
} }
export const logout = async (access_token) => { export const logout = async (access_token) => {

View File

@@ -0,0 +1,19 @@
import axios from 'axios';
let baseUrl = `${process.env.REACT_APP_API_SERVER}/api/lists`;
export function get_list(user_id) {
return axios.get(`${baseUrl}/list/${user_id}/`)
}
export function add_to_list(token, user_id, entity, entity_type, tags) {
const post_data = {
entity: entity,
entity_type: entity_type,
tags: tags
};
return axios.post(`${baseUrl}/list/${user_id}/`, post_data, {
headers: {'AUTHORIZATION': `Bearer ${token}`}
});
}

View File

@@ -26,3 +26,7 @@ export const toDuration = (miliseconds) => {
return pad2(minutes) + ':' + pad2(seconds) return pad2(minutes) + ':' + pad2(seconds)
} }
} }
export const wait = (miliseconds) => {
return new Promise(res => setTimeout(res, miliseconds))
}

View File

@@ -70,7 +70,10 @@ const Artist = (props) => {
if (artist){ if (artist){
return <Entity title={artist.name} return <Entity title={artist.name}
subtitle={[artist.type, artist.country].filter(Boolean).join(' - ')} subtitle={[artist.type, artist.country].filter(Boolean).join(' - ')}
tags={artist.tags.map((tag) => (tag.name))}/> tags={artist.tags.map((tag) => (tag.name))}
type={'artist'}
entity={artist.id}
/>
}else { }else {
return null; return null;
} }

View File

@@ -80,7 +80,10 @@ const Disc = (props) => {
) )
return <Entity title={disc.title} return <Entity title={disc.title}
subtitle={subtitle} subtitle={subtitle}
cover={<CoverArt disc={disc} popup={true} size={4}/>}/> cover={<CoverArt disc={disc} popup={true} size={4}/>}
type={'disc'}
entity={disc.id}
/>
}else { }else {
return <Fragment></Fragment> return <Fragment></Fragment>
} }

View File

@@ -70,7 +70,7 @@ const Release = (props) => {
</Link> </Link>
) )
return <Entity title={release.title} subtitle={subtitle} return <Entity title={release.title} subtitle={subtitle}
cover={<CoverArt release={release} popup={true} size={4}/>}/> cover={<CoverArt release={release} popup={true} size={4}/>} type={'release'} entity={release.id}/>
} else { } else {
return null; return null;
} }

View File

@@ -22,7 +22,7 @@ const Song = (props) => {
subtitle = <span>[{toDuration(song.length)}]</span> subtitle = <span>[{toDuration(song.length)}]</span>
} }
return <Entity title={song.title} subtitle={subtitle}/> return <Entity title={song.title} subtitle={subtitle} type={'song'} entity={song.id}/>
} }