From e07330e0b1f70e3e1e87fdc52a3f3b3c014ed545 Mon Sep 17 00:00:00 2001 From: Lucas Schoenfelder <les17@inf.ufpr.br> Date: Thu, 6 Feb 2020 17:19:53 -0300 Subject: [PATCH] terminando de adicionar chamadas para as rotas; adicionada animacao de carregando e tentando criar o modal de mudar avatar --- src/Components/ComponentAlterarAvatar.js | 226 +++++++++++++++++++++++ src/Components/LoadingSpinner.js | 11 ++ src/Components/LoginContainer.js | 16 +- src/Components/LoginContainerFunction.js | 6 +- src/Components/LoginModal.js | 12 +- src/Components/ModalAlterarAvatar.js | 81 +++----- src/Components/SignUpContainer.js | 2 +- src/Components/SignUpModal.js | 18 +- src/Components/TabPanelAtividades.js | 93 +++++++--- src/Components/TabPanelColecoes.js | 147 +++++++++++---- src/Components/TabPanelFavoritos.js | 181 +++++++++++++----- src/Components/TabPanelMeusRecursos.js | 196 +++++++++++++++----- src/Components/TabPanelRede.js | 10 +- src/Pages/UserPage.js | 36 +++- src/img/Bolo.png | Bin 0 -> 394 bytes src/img/loading_busca.gif | Bin 0 -> 10901 bytes 16 files changed, 787 insertions(+), 248 deletions(-) create mode 100644 src/Components/ComponentAlterarAvatar.js create mode 100644 src/Components/LoadingSpinner.js create mode 100644 src/img/Bolo.png create mode 100644 src/img/loading_busca.gif diff --git a/src/Components/ComponentAlterarAvatar.js b/src/Components/ComponentAlterarAvatar.js new file mode 100644 index 00000000..3115167a --- /dev/null +++ b/src/Components/ComponentAlterarAvatar.js @@ -0,0 +1,226 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, {useContext,Component} from 'react'; +import { Button } from '@material-ui/core'; +import Modal from '@material-ui/core/Modal'; +import Backdrop from '@material-ui/core/Backdrop'; +import Zoom from '@material-ui/core/Fade'; +import styled from 'styled-components' +import {Store} from '../Store.js' +import axios from 'axios' +import {apiUrl} from '../env'; +import CloseIcon from '@material-ui/icons/Close'; +import Profile from '../img/default_profile0.png' + +const ChangeAvatarDiv = styled.div` + color:rgba(255,255,255,.7); + background-color:rgba(0,0,0,.5); + position: absolute;bottom: 0; + width: inherit; + text-align: center; + font-size: 18px; + padding-bottom: 5px; + font-weight: 400; + height: 30%; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +` + +export default function ComponentAlterarAvatar (props) { + + + + return ( + <ModalDiv style={{maxWidth:"500px", maxHeight:"500px"}}> + <HeaderDiv> + <span style={{width:"32px"}}/> + <StyledH2>Editar Foto</StyledH2> + <StyledCloseModalButton onClick={props.handleClose}> + <CloseIcon/> + </StyledCloseModalButton> + </HeaderDiv> + <DialogDiv> + <div style={{marginTop:"0"}}> + <form> + <DivAlterarFoto> + <DivFlowHolder> + <AvatarCircleDiv> + { + props.userAvatar == '' || props.userAvatar == null ? + ( + + <img src={Profile} alt={'user avatar'} style={{height:"inherit", width:"inherit", objectFit:"cover"}}/> + ): + ( + <img src={this.props.userAvatar} alt={'user avatar'} style={{height:"inherit", width:"inherit", objectFit:"cover"}}/> + ) + } + <ChangeAvatarDiv> + <span>Alterar</span> + <input accept="image/*" id="icon-button-file" + type="file" + onChange={(e) => props.handleFile(e.target.files) }/> + </ChangeAvatarDiv> + </AvatarCircleDiv> + </DivFlowHolder> + </DivAlterarFoto> + <ButtonsDiv> + <ButtonCancelar><span>Cancelar</span></ButtonCancelar><ButtonConfirmar><span>Salvar Alterações</span></ButtonConfirmar> + </ButtonsDiv> + </form> + </div> + </DialogDiv> + </ModalDiv> + ) + +} + +const ModalDiv = styled.div` + background-color : #fff; + border-radius : 4px; + min-width : 560px; + color : #666; + display: flex; + flex-direction : column; +` + +const ButtonConfirmar = styled(Button)` + color : rgba(255,255,255,0.87); + background-color: rgb(0,188,212); + display: inline-block; + position: relative; + cursor: pointer; + min-height: 36px; + min-width: 88px; + line-height: 36px; + vertical-align: middle; + align-items: center; + text-align: center; + border-radius: 3px; + box-sizing: border-box; + user-select: none; + border: 0; + padding: 0 6px; + margin: 6px 8px; + background: transparent; + background-color: transparent; + color: currentColor; + white-space: nowrap; + text-transform: uppercase; + font-weight: 500; + font-size: 14px; + font-style: inherit; + font-variant: inherit; + font-family: inherit; + text-decoration: none; + overflow: hidden; +` + +const ButtonCancelar = styled(Button)` + outline : none; + display: inline-block; + position: relative; + cursor: pointer; + min-height: 36px; + min-width: 88px; + line-height: 36px; + vertical-align: middle; + align-items: center; + text-align: center; + border-radius: 3px; + box-sizing: border-box; + user-select: none; + border: 0; + padding: 0 6px; + margin: 6px 8px; + background:transparent; + color: currentColor; + white-space: nowrap; + text-transform: uppercase; + font-weight: 500; + font-size: 14px; + font-style: inherit; + font-variant: inherit; + font-family: inherit; + text-decoration: none; + overflow: hidden; +` + +const ButtonsDiv = styled.div` + display: flex; + justify-content:flex-end; +` + +const AvatarCircleDiv = styled.div` + margin-bottom : 0; + border-radius : 50%; + height : 150px; + width : 150px; + position : relative; +` + +const DivFlowHolder =styled.div` + align-self : auto; +` + +const DivAlterarFoto = styled.div` + display : flex; + margin-bottom : 30px; + flex-direction : row; + align-items : center; + justify-content :center; +` + +const DialogDiv = styled.div` + padding : 20px 30px; + overflow : visible !important; +` + +const HeaderDiv = styled.div` + display : flex; + flex-direction : row; + align-items : center; + align-content : center; + justify-content : center; + max-width : 100%; +` +const StyledH2 = styled.h2` + font-size : 26px; + font-weight : lighter; + margin-top : 20px; + margin-bottom : 10px; + font-family: inherit; + line-height: 1.1; + color: inherit; +` +const StyledCloseModalButton = styled(Button)` + display : inline-block; + position : relative; + float : right !important; + margin-right : -8px !important; + background : transparent !important; + min-width: 0 !important; + width : 40px; + border-radius : 50%; + padding : 8px; + height : 40px; + margin : 0 6px; +` diff --git a/src/Components/LoadingSpinner.js b/src/Components/LoadingSpinner.js new file mode 100644 index 00000000..ffeec909 --- /dev/null +++ b/src/Components/LoadingSpinner.js @@ -0,0 +1,11 @@ +import React from 'react'; +import LoadingGif from '../img/loading_busca.gif' + + const LoadingSpinner = (props) => ( + <div style={{display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center"}}> + <img src={LoadingGif} /> + <span style={{textTransform:"uppercase"}}>{props.text}</span> + </div> + ); + + export default LoadingSpinner; diff --git a/src/Components/LoginContainer.js b/src/Components/LoginContainer.js index 8546fc4c..ccdc075a 100644 --- a/src/Components/LoginContainer.js +++ b/src/Components/LoginContainer.js @@ -36,8 +36,8 @@ class LoginContainer extends Component { super(props); this.state = { - email : "", - senha : "", + email : localStorage.getItem("@portalmec/email") || "", + senha : localStorage.getItem("@portalmec/senha") ||"", checkboxChecked : false }; this.handleChecked = this.handleChecked.bind(this) @@ -55,8 +55,9 @@ class LoginContainer extends Component { onSubmit = (e) => { e.preventDefault(); + const login = this.state - this.props.handleLoginInfo(this.state); + this.props.handleLoginInfo(login); this.setState({ email: "", @@ -65,8 +66,11 @@ class LoginContainer extends Component { } handleChecked = (e) => { + const value = !this.state.checkboxChecked + console.log(this.state.checkboxChecked) + console.log(value) this.setState({ - checkboxChecked : !this.state.checkboxChecked + checkboxChecked :value }) } @@ -124,12 +128,12 @@ class LoginContainer extends Component { <br/> <RememberRecover> - <LabeledCheckbox label={<StyledLabel><StyledSpan>Lembrar-me</StyledSpan></StyledLabel>} onchange={this.handleChecked} disabledCheckbox={this.state.checkboxChecked}/> + <LabeledCheckbox label={<StyledLabel><StyledSpan>Lembrar-me</StyledSpan></StyledLabel>} handleChange={this.handleChecked} /> <UserForgotTheirPasswordSpan>Esqueceu a senha? <a href="recuperar-senha" style={{textAlign: "right", color:"#4cd0e1"}}>Clique aqui!</a></UserForgotTheirPasswordSpan> </RememberRecover> <ConfirmContainerStyled> - <StyledLoginButton onClick={e => this.onSubmit(e)} variant="contained"> + <StyledLoginButton type="submit" variant="contained"> <span style={{borderRadius:"3px", boxSizing:"border-box", fontFamily:"Roboto, sans serif", fontWeight:"500", color:"#fff"}}>ENTRAR</span> </StyledLoginButton> </ConfirmContainerStyled> diff --git a/src/Components/LoginContainerFunction.js b/src/Components/LoginContainerFunction.js index d1a5451b..c55da450 100644 --- a/src/Components/LoginContainerFunction.js +++ b/src/Components/LoginContainerFunction.js @@ -122,7 +122,7 @@ export default function LoginContainer (props) { } return ( - + <div> <ContainerStyled > <DialogHeaderStyled> <span style={{width:"32px"}}/> @@ -190,7 +190,7 @@ export default function LoginContainer (props) { </DialogFooterStyled> </DialogContentDiv> </ContainerStyled> - + </div> ) } @@ -202,7 +202,7 @@ export default function LoginContainer (props) { display : flex; flex-direction : column; min-width : 440px; - + align : center; padding : 10px; border-radius: 4px; diff --git a/src/Components/LoginModal.js b/src/Components/LoginModal.js index 26607d36..052084e7 100644 --- a/src/Components/LoginModal.js +++ b/src/Components/LoginModal.js @@ -79,14 +79,18 @@ export default function LoginModal (props){ } ) // console.log(response.headers) - // console.log(response.data) + console.log(login) // console.log(response.data.data.name) // console.log(response.data.data.uid) localStorage.setItem('@portalmec/accessToken', response.headers['access-token']) localStorage.setItem('@portalmec/clientToken', response.headers.client,) - localStorage.setItem('@portalmec/id', response.data.data.id) - localStorage.setItem('@portalmec/username', response.data.data.name) - localStorage.setItem('@portalmec/uid', response.data.data.uid) + sessionStorage.setItem('@portalmec/id', response.data.data.id) + sessionStorage.setItem('@portalmec/username', response.data.data.name) + sessionStorage.setItem('@portalmec/uid', response.data.data.uid) + if (login.checkboxChecked) { + localStorage.setItem('@portalmec/email', login.email) + localStorage.setItem('@portalmec/senha', login.senha) //MUDAR ISSO ASAP + } props.handleClose(); props.openSnackbar(); }, (error) => { diff --git a/src/Components/ModalAlterarAvatar.js b/src/Components/ModalAlterarAvatar.js index f770ebf1..8bd1c57e 100644 --- a/src/Components/ModalAlterarAvatar.js +++ b/src/Components/ModalAlterarAvatar.js @@ -20,24 +20,26 @@ import React, {useContext, useState} from 'react'; import { Button } from '@material-ui/core'; import Modal from '@material-ui/core/Modal'; import Backdrop from '@material-ui/core/Backdrop'; -import Zoom from '@material-ui/core/Fade'; +import Fade from '@material-ui/core/Fade'; import styled from 'styled-components' import {Store} from '../Store.js' import axios from 'axios' import {apiUrl} from '../env'; import CloseIcon from '@material-ui/icons/Close'; +import Profile from '../img/default_profile0.png' +import ComponentAlterarAvatar from './ComponentAlterarAvatar.js' const StyledModal = styled(Modal)` display : flex; - flex-direction : column; - border-radius : 4px; - background-color : #fff; - min-width : 560px; + align-items: center; + justify-content : center; + text-align : center; + padding : 10px !important; ` export default function ModarAlterarAvatar (props){ const {state, dispatch} = useContext(Store) - + const [avatarFile, setFile] = useState('') const handleLoginInfo = (login) => { axios.post(`${apiUrl}`, { @@ -47,13 +49,18 @@ export default function ModarAlterarAvatar (props){ }, (error) => { - } - ) + } + ) + }) } + const handleFile = (selectorFiles : FileList) => { + console.log(selectorFiles) + {/*setFile()*/} + } return ( - <> + <StyledModal aria-labelledby="transition-modal-title" aria-describedby="transition-modal-description" @@ -67,53 +74,15 @@ export default function ModarAlterarAvatar (props){ timeout: 500, }} > - <Zoom in={props.open} style={{ transitionDelay :"0.4ms"}}> - <HeaderDiv> - <span style={{width:"32px"}}/> - <StyledH2>Editar Foto</StyledH2> - <StyledCloseModalButton onClick={this.props.handleClose}> - <CloseIcon/> - </StyledCloseModalButton> - </HeaderDiv> - <DialogDiv> - </DialogDiv> - </Zoom> + <Fade in={props.open} style={{ transitionDelay :"0.4ms"}}> + + <ComponentAlterarAvatar + userAvatar={state.currentUser.userAvatar} + handleFile={handleFile} + handleClose={props.handleClose} + /> + </Fade> </StyledModal> - </> + ) } - -const DialogDiv = styled.div` - padding : 20px 30px; - overflow : visible !important; -` - -const HeaderDiv = styled.div` - display : flex; - flex-direction : row; - align-items : center; - align-content : center; - max-width : 100%; -` -const StyledH2 = styled.h2` - font-size : 26px; - font-weight : lighter; - margin-top : 20px; - margin-bottom : 10px; - font-family: inherit; - line-height: 1.1; - color: inherit; -` -const StyledCloseModalButton = styled(Button)` - display : inline-block; - position : relative; - float : right !important; - margin-right : -8px !important; - background : transparent !important; - min-width: 0 !important; - width : 40px; - border-radius : 50%; - padding : 8px; - height : 40px; - margin : 0 6px; -` diff --git a/src/Components/SignUpContainer.js b/src/Components/SignUpContainer.js index 21652735..9d01f58d 100644 --- a/src/Components/SignUpContainer.js +++ b/src/Components/SignUpContainer.js @@ -176,7 +176,7 @@ class SignUpContainer extends Component { onloadCallback={callback} /> <ConfirmContainerStyled> - <StyledSignUpButton onClick={e => this.onSubmit(e)} variant="contained"> + <StyledSignUpButton type="submit" variant="contained"> <span style={{paddingLeft:"16px", paddingRight:"16px", borderRadius:"3px", boxSizing:"border-box", fontFamily:"Roboto, sans serif", fontWeight:"500", color:"#fff"}} diff --git a/src/Components/SignUpModal.js b/src/Components/SignUpModal.js index cf38fc14..49386ce7 100644 --- a/src/Components/SignUpModal.js +++ b/src/Components/SignUpModal.js @@ -41,23 +41,7 @@ const StyledModalSignUp = styled(Modal)` ` -const useStyles = makeStyles(theme => ({ - modal: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - textAlign: "center", - maxBlockSize: "350px", - width: '100%', - minBlockSize: "100px", - }, - paper: { - backgroundColor: "theme.palette.background.paper", - border: '2px solid #000', - boxShadow: " 0px 3px 5px -1px rgba(0,0,0,0.2),0px 5px 8px 0px rgba(0,0,0,0.14),0px 1px 14px 0px rgba(0,0,0,0.12);", - align:"center", - }, -})); + export default function SignUpModal (props) { const { state, dispatch } = useContext(Store) diff --git a/src/Components/TabPanelAtividades.js b/src/Components/TabPanelAtividades.js index e26988af..074c12ed 100644 --- a/src/Components/TabPanelAtividades.js +++ b/src/Components/TabPanelAtividades.js @@ -5,29 +5,22 @@ import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; import axios from 'axios' import {apiUrl} from '../env'; +import Bolo from '../img/Bolo.png' +import LoadingSpinner from './LoadingSpinner.js' export default function TabPanelAtividades (props) { const [notifications, setNotifications] = useState([]); const [notificatonsLength, setLength] = useState(0); - - const config = { - headers : { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Access-Token': localStorage.getItem('@portalmec/accessToken'), - 'Client': localStorage.getItem('@portalmec/clientToken'), - 'Uid': localStorage.getItem('@portalmec/uid'), - 'Host': 'api.portalmec.c3sl.ufpr.br', - 'Cookie': '' - } - } + const [loading, handleLoading] = useState(true) useEffect( () => { - axios.get(`${apiUrl}/feed`, config) + axios.get(`${apiUrl}/feed`, props.config) .then( (response) => { - // console.log(response) - setNotifications(response) - setLength(response.length) + handleLoading(false) + console.log(response) + setNotifications(response.data) + console.log(response.data.length) + setLength(response.data.length) }, (error) => { console.log('error while running getNotifications') @@ -36,6 +29,7 @@ export default function TabPanelAtividades (props) { }, []) return ( + <ContainerDivStyled> <Paper elevation={3}> <div> @@ -46,17 +40,74 @@ export default function TabPanelAtividades (props) { </TituloContent> </InnerDivTitulo> </DivTitulo> - {/*some sort of map with a list of notifications idk what though*/} - <div> - <LoadMoreButton><span>CARREGAR MAIS 4</span></LoadMoreButton> - <LoadMoreButton><span>CARREGAR MAIS 20</span></LoadMoreButton> - </div> + { + loading ? + ( + <LoadingSpinner text={'Carregando Atividades'}/> + ) + : + ( + [ + <div> + { + notificatonsLength == 0 ? + ( + [ + <NoNotificationsDiv> + <div> + <div> + <H3Styled><img src={Bolo} alt='bolo' style={{width:"23px"}}/> Você se cadastrou na Plataforma</H3Styled> + </div> + <p + style={{fontSize:"15px", fontWeight:"lighter", margin:"0 0 10px", display:"flex", justifyContent:"center", textAlign:"center"}} + >Construa conosco a plataforma e amplie sua rede de conhecimento interagindo + <br/> + com pessoas envolvidas com experiências que ocorrem em todo o Brasil! + </p> + + </div> + </NoNotificationsDiv> + ] + ) + : + ( + [ <> + {/*some sort of map with a list of notifications idk what though*/} + <LoadMoreButton><span>CARREGAR MAIS 4</span></LoadMoreButton> + <LoadMoreButton><span>CARREGAR MAIS 20</span></LoadMoreButton> + </> + ] + ) + } + </div> + + ] + ) + } </div> </Paper> </ContainerDivStyled> ) } +const H3Styled = styled.h3` + font-size: 24px; + font-weight : lighter; + color : #00bcd4; + margin-top : 20px; + margin-bottom : 10px; + display : flex; + justify-content : center; + align-items : center; +` + +const NoNotificationsDiv = styled.div` + height : 250px; + display: flex; + align-items : center; + justify-content : center; +` + const LoadMoreButton = styled(Button)` outline : none !important; display : inline-block !important; diff --git a/src/Components/TabPanelColecoes.js b/src/Components/TabPanelColecoes.js index 85d97654..b865f48b 100644 --- a/src/Components/TabPanelColecoes.js +++ b/src/Components/TabPanelColecoes.js @@ -1,44 +1,123 @@ -import React, {useContext} from 'react' +import React, {useContext, useState, useEffect} from 'react' import styled from 'styled-components' import { Container } from 'react-grid-system' import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; import {ButtonMostrarTodos, ButtonMostrarMais, BtnAlinhaRecPvt, DivContainerRecursosPublicados, ContainerDivStyled, DivTitulo, StyledP, StyledHR} from './TabPanelMeusRecursos.js' +import {NoPubSpan, DivConteudoNaoPublicado, DivTextoNoPublications} from './TabPanelMeusRecursos.js' +import LoadingSpinner from './LoadingSpinner.js' +import axios from 'axios' +import {apiUrl} from '../env'; export default function TabPanelColecoes (props) { + const [loading, handleLoading] = useState(true) + + const [userCollections, setuserCollections] = useState([]) + const [userCollectionsLength, setuserCollectionsLength] = useState(0) + + const [followedCollections, setFollowedCollections] = useState([]) + const [followedCollectionsLength, setfollowedCollectionsLength] = useState(0) + + useEffect( () => { + axios.all([ + axios.get((`${apiUrl}/users/` + props.id + '/collections'), props.config), + axios.get((`${apiUrl}/users/` + props.id + '/following/Collection'), props.config), + ]) + .then( (responseArr) => { + handleLoading(false) + console.log(responseArr) + console.log(responseArr[0].data) + console.log(responseArr[1].data) + }, + (error) => { + handleLoading(false) + console.log('error while running axios all') + } + ) + }, []) + return ( - <React.Fragment> - <ContainerDivStyled> - <Paper elevation={3}> - <DivTitulo> - <StyledP>Minhas Coleções <b style={{fontWeight:"700", fontSize:"20px"}}>(2)</b></StyledP> - <StyledHR/> - </DivTitulo> - <div style={{height : "400px"}}> //REMOVER ISSO - <DivContainerRecursosPublicados> - </DivContainerRecursosPublicados> - <BtnAlinhaRecPvt> - <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 2 de 2</p> - </BtnAlinhaRecPvt> - </div> - </Paper> - </ContainerDivStyled> - - <ContainerDivStyled> - <Paper elevation={3}> - <DivTitulo> - <StyledP>Coleções que eu sigo <b style={{fontWeight:"700", fontSize:"20px"}}>(4)</b></StyledP> - <StyledHR/> - </DivTitulo> - <div style={{height : "400px"}}> //REMOVER ISSO - <DivContainerRecursosPublicados> - </DivContainerRecursosPublicados> - <BtnAlinhaRecPvt> - <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 4 de 4</p> - </BtnAlinhaRecPvt> - </div> - </Paper> - </ContainerDivStyled> - </React.Fragment> + <> + { + loading ? + ( + <LoadingSpinner text={'CARREGANDO COLEÇÕES'}/> + + ) + : + ( + [ + <React.Fragment> + <ContainerDivStyled> + <Paper elevation={3}> + <DivTitulo> + <StyledP>Minhas Coleções <b style={{fontWeight:"700", fontSize:"20px"}}>({userCollectionsLength})</b></StyledP> + <StyledHR/> + </DivTitulo> + <div style={{height : "400px"}}> + { + userCollectionsLength == 0 ? + ( + [ + <span>Adicionar tela "criar colecao" e a imagem</span> + ] + ) + : + ( + <> + <DivContainerRecursosPublicados> + </DivContainerRecursosPublicados> + <BtnAlinhaRecPvt> + <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 2 de 2</p> + </BtnAlinhaRecPvt> + </> + ) + } + + </div> + </Paper> + </ContainerDivStyled> + + <ContainerDivStyled> + <Paper elevation={3}> + <DivTitulo> + <StyledP>Coleções que eu sigo <b style={{fontWeight:"700", fontSize:"20px"}}>({followedCollectionsLength})</b></StyledP> + <StyledHR/> + </DivTitulo> + <div style={{height : "400px"}}> + { + followedCollectionsLength == 0 ? + ( + [ + <> + <DivTextoNoPublications> + <DivConteudoNaoPublicado> + <NoPubSpan>Você ainda não segue nenhuma coleção.</NoPubSpan> + </DivConteudoNaoPublicado> + </DivTextoNoPublications> + </> + ] + ) + : + ( + [ + <> + <DivContainerRecursosPublicados> + </DivContainerRecursosPublicados> + <BtnAlinhaRecPvt> + <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 4 de 4</p> + </BtnAlinhaRecPvt> + </> + ] + ) + } + </div> + </Paper> + </ContainerDivStyled> + </React.Fragment> + ] + ) + } + </> ) } diff --git a/src/Components/TabPanelFavoritos.js b/src/Components/TabPanelFavoritos.js index 1615309d..71c3e50a 100644 --- a/src/Components/TabPanelFavoritos.js +++ b/src/Components/TabPanelFavoritos.js @@ -1,57 +1,148 @@ -import React, {useContext} from 'react' +import React, {useContext, useState, useEffect} from 'react' import styled from 'styled-components' import { Container } from 'react-grid-system' import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; +import axios from 'axios' +import {apiUrl} from '../env'; import {ButtonMostrarTodos, ButtonMostrarMais, BtnAlinhaRecPvt, DivContainerRecursosPublicados, ContainerDivStyled, DivTitulo, StyledP, StyledHR} from './TabPanelMeusRecursos.js' +import {NoPubSpan, DivConteudoNaoPublicado, DivTextoNoPublications} from './TabPanelMeusRecursos.js' +import LoadingSpinner from './LoadingSpinner.js' export default function TabPanelFavoritos (props) { + const [loading, handleLoading] = useState(true) + + const [likedLearnObjs, setlikedLearnObjs] = useState([]) + const [likedLearnObjsLength, setlikedLearnObjsLength] = useState(0) + + const [likedCollections, setlikedCollections] = useState([]) + const [likedCollectionsLength, setlikedCollectionsLength] = useState(0) + + useEffect( () => { + axios.all([ + axios.get((`${apiUrl}/users/` + props.id + '/learning_objects/liked'), props.config), + axios.get((`${apiUrl}/users/` + props.id + '/collections/liked'), props.config), + ]) + .then( (responseArr) => { + handleLoading(false) + console.log(responseArr) + console.log(responseArr[0].data) + console.log(responseArr[1].data) + }, + (error) => { + handleLoading(false) + console.log('error while running axios all') + } + ) + }, []) + return ( - <React.Fragment> - <ContainerDivStyled> - <Paper elevation={3}> - <DivTitulo> - <StyledP>Recursos Favoritados <b style={{fontWeight:"700", fontSize:"20px"}}>(43)</b></StyledP> - <StyledHR/> - </DivTitulo> - <div style={{height : "400px"}}> //REMOVER ISSO - <DivContainerRecursosPublicados> - </DivContainerRecursosPublicados> - <BtnAlinhaRecPvt> - <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 4 de 7</p> - <ButtonMostrarMais> - <span style={{color:"#fff", fontSize:"14px", fontWeight:"500"}}>MOSTRAR MAIS</span> - </ButtonMostrarMais> - <ButtonMostrarTodos> - <span style={{color:"#666", fontSize:"14px", fontWeight:"500"}}>MOSTRAR TODOS</span> - </ButtonMostrarTodos> - </BtnAlinhaRecPvt> - </div> - </Paper> - </ContainerDivStyled> + <> + { + loading? + ( + <LoadingSpinner text={'CARREGANDO...'}/> + ) + : + ( + [ + <React.Fragment> + <ContainerDivStyled> + <Paper elevation={3}> + <DivTitulo> + <StyledP>Recursos Favoritados <b style={{fontWeight:"700", fontSize:"20px"}}>({likedLearnObjsLength})</b></StyledP> + <StyledHR/> + </DivTitulo> + <div style={{height : "400px"}}> + { + likedLearnObjsLength == 0 ? + ( + [ + <> + <DivTextoNoPublications> + <DivConteudoNaoPublicado> + <NoPubSpan>Você ainda não curtiu nenhum Recurso.</NoPubSpan> + <p style={{fontFamily:"Roboto",fontSize:"16px"}}>Quando você favorita um recurso ele aparece nesta seção. Além disso, você + <br/> + aumenta o prestígio dele na Plataforma. Para favoritar, basta clicar no ícone de + <br/> + coração que aparece nos Recursos. + </p> + </DivConteudoNaoPublicado> + </DivTextoNoPublications> + </> + ] + ) + : + ( + [ + <> + <DivContainerRecursosPublicados> + </DivContainerRecursosPublicados> + <BtnAlinhaRecPvt> + <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 4 de 7</p> + <ButtonMostrarMais> + <span style={{color:"#fff", fontSize:"14px", fontWeight:"500"}}>MOSTRAR MAIS</span> + </ButtonMostrarMais> + <ButtonMostrarTodos> + <span style={{color:"#666", fontSize:"14px", fontWeight:"500"}}>MOSTRAR TODOS</span> + </ButtonMostrarTodos> + </BtnAlinhaRecPvt> + </> + ] + ) + } + </div> + </Paper> + </ContainerDivStyled> - <ContainerDivStyled> - <Paper elevation={3}> - <DivTitulo> - <StyledP>Recursos Favoritados <b style={{fontWeight:"700", fontSize:"20px"}}>(43)</b></StyledP> - <StyledHR/> - </DivTitulo> - <div style={{height : "400px"}}> //REMOVER ISSO - <DivContainerRecursosPublicados> - </DivContainerRecursosPublicados> - <BtnAlinhaRecPvt> - <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 4 de 7</p> - <ButtonMostrarMaisColecao> - <span style={{color:"#fff", fontSize:"14px", fontWeight:"500"}}>MOSTRAR MAIS</span> - </ButtonMostrarMaisColecao> - <ButtonMostrarTodos> - <span style={{color:"#666", fontSize:"14px", fontWeight:"500"}}>MOSTRAR TODOS</span> - </ButtonMostrarTodos> - </BtnAlinhaRecPvt> - </div> - </Paper> - </ContainerDivStyled> - </React.Fragment> + <ContainerDivStyled> + <Paper elevation={3}> + <DivTitulo> + <StyledP>Coleções Favoritadas <b style={{fontWeight:"700", fontSize:"20px"}}>({likedCollectionsLength})</b></StyledP> + <StyledHR/> + </DivTitulo> + <div style={{height : "400px"}}> + { + likedCollectionsLength == 0 ? + ( + [ + <> + <DivTextoNoPublications> + <DivConteudoNaoPublicado> + <NoPubSpan>Você ainda não curtiu nenhuma coleção.</NoPubSpan> + </DivConteudoNaoPublicado> + </DivTextoNoPublications> + </> + ] + ) + : + ( + [ + <> + <DivContainerRecursosPublicados> + </DivContainerRecursosPublicados> + <BtnAlinhaRecPvt> + <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 4 de 7</p> + <ButtonMostrarMaisColecao> + <span style={{color:"#fff", fontSize:"14px", fontWeight:"500"}}>MOSTRAR MAIS</span> + </ButtonMostrarMaisColecao> + <ButtonMostrarTodos> + <span style={{color:"#666", fontSize:"14px", fontWeight:"500"}}>MOSTRAR TODOS</span> + </ButtonMostrarTodos> + </BtnAlinhaRecPvt> + </> + ] + ) + } + </div> + </Paper> + </ContainerDivStyled> + </React.Fragment> + ] + ) + } + </> ) } diff --git a/src/Components/TabPanelMeusRecursos.js b/src/Components/TabPanelMeusRecursos.js index d43f9643..1f2b432b 100644 --- a/src/Components/TabPanelMeusRecursos.js +++ b/src/Components/TabPanelMeusRecursos.js @@ -5,66 +5,160 @@ import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; import axios from 'axios' import {apiUrl} from '../env'; +import LoadingSpinner from './LoadingSpinner.js' export default function TabPanelAtividades (props) { + const [loading, handleLoading] = useState(true) + + const [learningObjects, setLearningObjects] = useState([]); + const [learningObjectsLength, setLengthLearnObj] = useState(0); + + const [drafts, setDrafts] = useState([]); + const [draftsLength, setLengthDrafts] = useState(0); + + const [curating, setCurating] = useState([]); + const [curatingLength, setLengthCurating] = useState(0); + + useEffect( () => { + axios.all([ + axios.get((`${apiUrl}/users/` + props.id + '/learning_objects'), props.config), + axios.get((`${apiUrl}/users/` + props.id + '/drafts'), props.config), + axios.get((`${apiUrl}/users/` + props.id + '/submissions?stats=submitted'), props.config) + ]) + .then( (responseArr) => { + handleLoading(false) + console.log(responseArr) + console.log(responseArr[0].data) + console.log(responseArr[1].data) + console.log(responseArr[2].data) + + }, + (error) => { + handleLoading(false) + console.log('error while running axios all') + } + ) + }, []) + + return ( - <React.Fragment> - <ContainerDivStyled> - <Paper elevation={3}> - - <DivTitulo> - <StyledP>Recurso Publicado <b style={{fontWeight:"700", fontSize:"20px"}}>(0)</b></StyledP> - <StyledHR/> - </DivTitulo> - <div> - <DivTextoNoPublications> + <> + { + loading ? + ( + <LoadingSpinner text={'Carregando Recursos'}/> + ) + : + ([ + <React.Fragment> + <ContainerDivStyled> + <Paper elevation={3}> + + <DivTitulo> + <StyledP>Recurso Publicado <b style={{fontWeight:"700", fontSize:"20px"}}>({learningObjectsLength})</b></StyledP> + <StyledHR/> + </DivTitulo> + <div> + { + learningObjectsLength == 0 ? + ( + [ + <> + <DivTextoNoPublications> <DivConteudoNaoPublicado> - <NoPubSpan>Você ainda não publicou nenhum Recurso!</NoPubSpan> + <NoPubSpan>Você ainda não publicou nenhum Recurso!</NoPubSpan> </DivConteudoNaoPublicado> - </DivTextoNoPublications> - </div> - </Paper> - </ContainerDivStyled> - - <ContainerDivStyled> - <Paper elevation={3}> - <DivTitulo> - <StyledP>Rascunhos<b style={{fontWeight:"700", fontSize:"20px"}}>(7)</b></StyledP> - <StyledHR/> - </DivTitulo> - <div style={{height : "400px"}}> //REMOVER ISSO - <DivContainerRecursosPublicados> - </DivContainerRecursosPublicados> - <BtnAlinhaRecPvt> + </DivTextoNoPublications> + </> + ] + ) + : + ( + <> + {/**some sort of map with a list of notifications idk what though**/} + <span>stuff goes here</span> + </> + ) + + } + </div> + </Paper> + </ContainerDivStyled> + + <ContainerDivStyled> + <Paper elevation={3}> + <DivTitulo> + <StyledP>Rascunhos <b style={{fontWeight:"700", fontSize:"20px"}}>({draftsLength})</b></StyledP> + <StyledHR/> + </DivTitulo> + <div style={{height : "400px"}}> + { + draftsLength == 0 ? + ( + [ + <> + <DivTextoNoPublications> + <DivConteudoNaoPublicado> + <NoPubSpan>Você não tem nenhum recurso sendo editado.</NoPubSpan> + </DivConteudoNaoPublicado> + </DivTextoNoPublications> + </> + ] + ) + : ( + [ + <> + <DivContainerRecursosPublicados> + </DivContainerRecursosPublicados> + <BtnAlinhaRecPvt> <p style={{margin:"0 0 10px", fontSize:"14px"}}>Carregados 4 de 7</p> <ButtonMostrarMais> - <span style={{color:"#fff", fontSize:"14px", fontWeight:"500"}}>MOSTRAR MAIS</span> + <span style={{color:"#fff", fontSize:"14px", fontWeight:"500"}}>MOSTRAR MAIS</span> </ButtonMostrarMais> <ButtonMostrarTodos> - <span style={{color:"#666", fontSize:"14px", fontWeight:"500"}}>MOSTRAR TODOS</span> + <span style={{color:"#666", fontSize:"14px", fontWeight:"500"}}>MOSTRAR TODOS</span> </ButtonMostrarTodos> - </BtnAlinhaRecPvt> - </div> - </Paper> - </ContainerDivStyled> - - <ContainerDivStyled> - <Paper elevation={3}> - - <DivTitulo> - <StyledP>Recurso sendo avaliado pela curadoria <b style={{fontWeight:"700", fontSize:"20px"}}>(0)</b></StyledP> - <StyledHR/> - </DivTitulo> - <div> + </BtnAlinhaRecPvt> + </> + ] + ) + } + </div> + </Paper> + </ContainerDivStyled> + + <ContainerDivStyled> + <Paper elevation={3}> + + <DivTitulo> + <StyledP>Recurso sendo avaliado pela curadoria <b style={{fontWeight:"700", fontSize:"20px"}}>({curatingLength})</b></StyledP> + <StyledHR/> + </DivTitulo> + <div> + { + curatingLength == 0 ? + ( <DivTextoNoPublications> - <DivConteudoNaoPublicado> - <NoPubSpan>Você não tem nenhum recurso sendo avaliado pelos curadores.</NoPubSpan> - </DivConteudoNaoPublicado> + <DivConteudoNaoPublicado> + <NoPubSpan>Você não tem nenhum recurso sendo avaliado pelos curadores.</NoPubSpan> + </DivConteudoNaoPublicado> </DivTextoNoPublications> - </div> - </Paper> - </ContainerDivStyled> - </React.Fragment> + ) + : + ( + <> + {/**some sort of map with a list of notifications idk what though**/} + <span>stuff goes here</span> + </> + ) + } + </div> + </Paper> + </ContainerDivStyled> + </React.Fragment> + ]) + } + </> ) } @@ -118,19 +212,19 @@ export const DivContainerRecursosPublicados = styled.div` padding-left : 15px; ` -const NoPubSpan = styled.span` +export const NoPubSpan = styled.span` font-size : 24px; font-family : Roboto; font-weight : lighter; ` -const DivConteudoNaoPublicado = styled.div` +export const DivConteudoNaoPublicado = styled.div` position : relative; top : 50%; transform : translateY(-50%); ` -const DivTextoNoPublications = styled.div` +export const DivTextoNoPublications = styled.div` height : 360px; text-align : center; width : 100%; diff --git a/src/Components/TabPanelRede.js b/src/Components/TabPanelRede.js index cebbf197..e3f3aa61 100644 --- a/src/Components/TabPanelRede.js +++ b/src/Components/TabPanelRede.js @@ -1,11 +1,15 @@ -import React, {useContext} from 'react' +import React, {useContext, useState, useEffect} from 'react' import styled from 'styled-components' import { Container } from 'react-grid-system' import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; import {ButtonMostrarTodos, ButtonMostrarMais, BtnAlinhaRecPvt, DivContainerRecursosPublicados, ContainerDivStyled, DivTitulo, StyledP, StyledHR} from './TabPanelMeusRecursos.js' +import axios from 'axios' +import {apiUrl} from '../env'; export default function TabPanelRede (props) { + + return ( <React.Fragment> <ContainerDivStyled> @@ -14,7 +18,7 @@ export default function TabPanelRede (props) { <StyledP>Seguidor <b style={{fontWeight:"700", fontSize:"20px"}}>(1)</b></StyledP> <StyledHR/> </DivTitulo> - <div style={{height : "400px"}}> //REMOVER ISSO + <div style={{height : "400px"}}> <DivContainerRecursosPublicados> </DivContainerRecursosPublicados> <BtnAlinhaRecPvt> @@ -30,7 +34,7 @@ export default function TabPanelRede (props) { <StyledP>Seguindo <b style={{fontWeight:"700", fontSize:"20px"}}>(1)</b></StyledP> <StyledHR/> </DivTitulo> - <div style={{height : "400px"}}> //REMOVER ISSO + <div style={{height : "400px"}}> <DivContainerRecursosPublicados> </DivContainerRecursosPublicados> <BtnAlinhaRecPvt> diff --git a/src/Pages/UserPage.js b/src/Pages/UserPage.js index d8fef866..e39dbe78 100644 --- a/src/Pages/UserPage.js +++ b/src/Pages/UserPage.js @@ -39,6 +39,7 @@ import TabPanelColecoes from '../Components/TabPanelColecoes.js' import TabPanelRede from '../Components/TabPanelRede.js' import axios from 'axios' import {apiUrl} from '../env'; +import ModarAlterarAvatar from '../Components/ModalAlterarAvatar.js' export default function UserPage (props){ const {state, dispatch} = useContext(Store) @@ -46,6 +47,23 @@ export default function UserPage (props){ const [value, setValue] = useState(0); const user = localStorage.getItem('@portalmec/username') const id = localStorage.getItem('@portalmec/id') + const [modalOpen, handleModal] = useState(false) + + const config = { + headers : { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Access-Token': localStorage.getItem('@portalmec/accessToken'), + 'Client': localStorage.getItem('@portalmec/clientToken'), + 'Uid': sessionStorage.getItem('@portalmec/uid'), + 'Host': 'api.portalmec.c3sl.ufpr.br', + 'Cookie': '' + } + } + + const modalControl = () => { + handleModal(!modalOpen) + } const handleHoverAlterarFoto = () => { handleAlterarFoto(!hoverAlterarFoto) @@ -97,6 +115,10 @@ export default function UserPage (props){ ( [ <React.Fragment> + <ModarAlterarAvatar + open={modalOpen} + handleClose={modalControl} + /> <HeaderDiv> <ContainerNoPad> <BreadcrumbsDiv> @@ -129,7 +151,7 @@ export default function UserPage (props){ </Tooltip> </label> </CoverContainer> - <ProfileAvatarDiv onMouseEnter={handleHoverAlterarFoto} onMouseLeave={handleHoverAlterarFoto}> + <ProfileAvatarDiv onMouseEnter={handleHoverAlterarFoto} onMouseLeave={handleHoverAlterarFoto} onClick={modalControl}> <img src={state.currentUser.userAvatar} alt = "user avatar" style={{border:"0", verticalAlign:"middle"}}/> <ChangeAvatarDiv style={ {display : hoverAlterarFoto ? 'flex' : 'none'}}> <span>Alterar Foto</span> @@ -198,11 +220,11 @@ export default function UserPage (props){ </Paper> </MainContainerDesktop> </div> - {value === 0 && <TabPanelAtividades/>} - {value === 1 && <TabPanelMeusRecursos/>} - {value === 2 && <TabPanelFavoritos/>} - {value === 3 && <TabPanelColecoes/>} - {value === 4 && <TabPanelRede/>} + {value === 0 && <TabPanelAtividades id={id} config={config}/>} + {value === 1 && <TabPanelMeusRecursos id={id} config={config}/>} + {value === 2 && <TabPanelFavoritos id={id} config={config}/>} + {value === 3 && <TabPanelColecoes id={id} config={config}/>} + {value === 4 && <TabPanelRede id={id} config={config}/>} </ContainerNoPad> </HeaderDiv> </React.Fragment> @@ -279,7 +301,7 @@ export default function UserPage (props){ outline : 0; cursor : pointer; ` - const ChangeAvatarDiv = styled.div` + export const ChangeAvatarDiv = styled.div` height : 40px; position: absolute; width : 100%; diff --git a/src/img/Bolo.png b/src/img/Bolo.png new file mode 100644 index 0000000000000000000000000000000000000000..509d2acae8705430a4707bb88fc07bd04ae33999 GIT binary patch literal 394 zcmeAS@N?(olHy`uVBq!ia0y~yU=U_tU=ZhEV_;x-d9dUb0|NtRfk$L90|U1(2s1Lw znj^u$z#v)T8c`CQpH@<ySd_}(n3A8As^FQMn4TJxnwU~qcrw+7fq_xn)5S5Q;?|jv zgMv*40xZ%Kw%2`l>ulAqk;%J5b_vUy140X!G98ZcRlJaw|F@NCPqNtYL?NbGg?nbo z78OrFyRp18ICSde)|3C<2p0v*B|n*=tJZS)u<I<L*t9E>f-YYy57i3%xm{Fu{poBi z;r!QziC3rQD1DCF_n-Hs*#6^Xsw(o?Y<G9;YS?nhm-ofxvpusNmTctRbT90`n%~qH zHv*D%RZmXXX>#|@ySc50(v(9|4!4A!ElJ6CTGhF9m&D|!T#I^GM7w-7kAxld%4=Z1 z%j8qj$jp_K#s5LuZQp^~r-y~r9&gleR1(Z_d(xubkt}$v``Xu2fgZ7v)(ctxn%lp8 xRzE>itm4Pn)3dteG~%u*hF!{-;?Ky&;GQ$dXotkZnG6gJ44$rjF6*2UngC`ipP2vv literal 0 HcmV?d00001 diff --git a/src/img/loading_busca.gif b/src/img/loading_busca.gif new file mode 100644 index 0000000000000000000000000000000000000000..3ce6d59693025fbd428c063f7f565a858c20d4db GIT binary patch literal 10901 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%0DhW)+ec)w#uSN<-|1A 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 literal 0 HcmV?d00001 -- GitLab