Skip to content

Commit

Permalink
Merge pull request #115 from DemocraciaEnRed/dev
Browse files Browse the repository at this point in the history
Ready for 1.9.0
  • Loading branch information
guillecro authored Dec 2, 2020
2 parents 12d8275 + ef816b1 commit b39138c
Show file tree
Hide file tree
Showing 56 changed files with 759 additions and 55 deletions.
35 changes: 34 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@

# Changelog

### 1.9.0

1. Nueva funcion: Apoyar un proyecto. Ahora se puede apoyar un proyecto, como usuario registrado o como anonimo (con la necesidad de ingresar la informacion, un captcha, y una validacion por email)
2. Se agrega la posibilida de que en el perfil del diputada/o se pueda descargar la planilla con un listado de la informacion de quienes apoyaron los proyectos
3. Cambios visuales: Ahora los header de las cards de los proyectos tendrán una imagen de -LA PRIMERA- etiqueta/categoria elegida en el formulario del proyecto. En el caso de que no existiera, se pondrá una imagen estandar

Listado de cambios:

- Agregado campo "apoyos" en Documents como array de strings (emails)
- Agregado campo "apoyosCount" en las apis que devuelven Documents (para mostrar en tarjetas de home y dentro del proyecto)
- Se implementó un método de captcha para apoyos externos
- Se desarrollo un circuito de validación de apoyo externo (usuarix no registradx) por email usando tabla de tokens
- Al apoyar, se valida que ese mail no haya apoyado ya una vez
- Al apoyar, se valida que ese mail no tenga un token de validación vigente (creado en las últimas 48hs)
- Verificacion del captcha! (svg-captcha https://www.npmjs.com/package/svg-captcha)
- Usuarix no registradxs: Al poner mail y nombre se le avisa a lx usuarix de que va a recibir un mail para validar
- Se genera token de un solo uso (que "caduque" a las 48hs) para validar voto
- Se crea la tabla apoyoTokens con campos: token, fecha creación, email
- El token se generará como uuid v4 (https://www.npmjs.com/package/uuid)
- En el script init borraran los token más viejos que de 48hs
- Enviar mail (desde notifier) con link con el token en la url para validar el voto
- Cuando alguien X entré a validar el token, se verifica que este en el rango de 48hs (sino se avisa que ya expiró), y si lo está se registra un apoyo a nombre del mail del token y se borra el token
- Para apoyos internos (usuarix registradx) simplemente registrar el apoyo y ya
- Se agrega la posibilidad de descargar un excel con todos los apoyos registrados en los proyectos.
- Se quito el campo de URL de la imagen, dado que no va a ser mas utlilzado
- Ahora las etiquetas cuentan con una "key", importante para coordinar con que imagen mostrar en el header de la card del proyecto
- Ahora para monitores mas grandes-largos (2K/4K) se muestran 4 columnas de cards de proyectos en la home

Compatible con:
* `leyesabiertas-web:1.9.0`
* `leyesabiertas-core:1.9.0`
* `leyesabiertas-notifier:1.9.0`
* `leyesabiertas-keycloak:1.8.0`

### 1.8.1

* Cambiado la extension de excel de xls a xlsx
Expand All @@ -10,7 +44,6 @@ Compatible con:
* `leyesabiertas-notifier:1.8.0`
* `leyesabiertas-keycloak:1.8.0`


### 1.8.0

Listado de cambios hasta el momento:
Expand Down
265 changes: 265 additions & 0 deletions components/apoyar-formulario/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import React, { Component, Fragment } from 'react'
import styled from 'styled-components'
import fetch from 'isomorphic-unfetch'
import WithUserContext from '../../components/with-user-context/component'
import getConfig from 'next/config'
const { publicRuntimeConfig: { API_URL } } = getConfig()

const Container = styled.form`
z-index: 2
position: absolute
width: 330px
right: 0
background-color: white
padding: 1.2em
box-shadow: 0px 2px 4px #cac7c7
color: black
text-align: left
text-transform: none
cursor: auto;
margin-top: 7px;
font-size:1.7rem
@media(max-width:700px){
position: fixed;
bottom: 0;
left: 0;
width: 100%;
}
input {
padding: 5px;
}
`

const Label = styled.label`
display: block
span {
display: block
font-weight: bold
padding: 14px 0px 7px
}
input {
width: 100%
}
`

const ApoyosSpan = styled.span`
color: #6f78e6
font-weight: bold
`

const ApoyarButton = styled.button`
width: 100%
padding: 13px 0;
background-color: #6f78e6
color: white
font-weight: bold
border: none
:focus {outline:0;}
display: inline-flex;
align-items: center;
justify-content: center;
img{
position: relative;
top: 1px;
margin-right: 5px;
}
`
const CloseButton = styled.div`
width: 65px;
margin-left: auto;
margin-bottom: 7px;
background-color: transparent;
border: none;
color: #960c0c;
cursor: pointer;
font-weight: bold;
font-size: 1.3rem;
@media(max-width:700px){
font-size: 1.7rem;
}
`

const CaptchaGroup = styled.div`
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
img {
padding-bottom: 5px;
}
input {
width: 70px;
text-align: center;
/*text-transform: uppercase;*/
}
`

const CaptchaTitle = styled.span`
font-weight: bold;
`

const ErrorSpan = styled.span`
display: block;
color:red
padding-bottom: 5px;
margin-top: 20px;
`

const ApoyandoGroup = styled.div`
text-align: center
display: flex;
flex-direction: column;
img {
margin-bottom: 18px;
height: 40px;
}
`

const ApoyandoSpan = styled.span`
font-weight: bold;
margin-bottom: 8px;
`

const ApoyandoPersonasSpan = styled.span`
color: #6f78e6
font-weight: bold;
`

class ApoyarFormulario extends Component {
state = {
formError: null,
svg: null,
token: null,
nombre_apellido: '',
email: '',
captcha: '',
}

constructor (props) {
super(props)

this.nombreApellidoInput = this.nombreApellidoInput.bind(this)
this.emailInput = this.emailInput.bind(this)
this.captchaInput = this.captchaInput.bind(this)
this.closeClick = this.closeClick.bind(this)

this.handleSubmit = this.handleSubmit.bind(this)
}

nombreApellidoInput(e) { this.setState({ nombre_apellido: e.target.value }) }
emailInput(e) { this.setState({ email: e.target.value }) }
captchaInput(e) { this.setState({ captcha: e.target.value }) }

handleSubmit(e){
e.preventDefault()

const { authenticated } = this.props.authContext

this.props.apoyarProyecto(!authenticated && {
token: this.state.token,
nombre_apellido: this.state.nombre_apellido,
email: this.state.email,
captcha: this.state.captcha,
}).then(async (res) => {
if (res.status == 200){
this.setState({formError: null})
if (!authenticated)
this.props.apoyoAnonExitoso()
}else{
let err
try {
err = (await res.json()).error
} catch(_) {
err = "Ha ocurrido un error"
}
this.setState({formError: err})
}
})
}

closeClick(){
const hideApoyarForm = localStorage.getItem('hide-apoyar-form') || false
if (!hideApoyarForm)
localStorage.setItem('hide-apoyar-form', true)
this.props.toggleFormulario()
}

componentWillMount() {
if (!this.props.authContext.authenticated && !this.state.svg) {
fetch(`${API_URL}/api/v1/documents/captcha-data`)
.then(r => r.json())
.then(j => this.setState({svg: j.img, token: j.token}))
}
}

render () {
const { authenticated, user } = this.props.authContext
const { project, hasAnonApoyado } = this.props
const { svg } = this.state

if (!project) return null

const apoyosMinusOne = project.apoyosCount-1

return (
<Container onSubmit={this.handleSubmit}>
<CloseButton onClick={this.closeClick}>CERRAR ✖</CloseButton>
{ project.userIsApoyado &&
<ApoyandoGroup>
<img src={`${'/static/assets/corazon.svg'}`} />
<ApoyandoSpan>¡Ya estás apoyando esta propuesta!</ApoyandoSpan>
{project.apoyosCount > 1 &&
<Fragment>
<ApoyandoPersonasSpan>{ apoyosMinusOne } {apoyosMinusOne == 1 ? 'persona' : 'personas'} y vos</ApoyandoPersonasSpan>
<span>Están apoyando la propuesta</span>
</Fragment>
}
</ApoyandoGroup>
}
{ hasAnonApoyado &&
<ApoyandoGroup>
<img src={`${'/static/assets/corazon.svg'}`} />
<ApoyandoSpan>¡Gracias!</ApoyandoSpan>
<Fragment>
<span>Enviamos un mail a su casilla para validar su apoyo</span>
</Fragment>
</ApoyandoGroup>
}
{ !hasAnonApoyado && !project.userIsApoyado &&
<Fragment>
<ApoyosSpan>{ project.apoyosCount || 0 } personas</ApoyosSpan> están apoyando la propuesta<br />
¿Querés apoyarla también?
{ !authenticated &&
<Fragment>
<Label>
<span>Nombre y Apellido</span>
<input name="nombre_apellido" required value={this.state.nombre_apellido} onChange={this.nombreApellidoInput} />
</Label>
<Label>
<span>Email</span>
<input name="email" type="email" required value={this.state.email} onChange={this.emailInput} />
</Label>
<CaptchaGroup>
<CaptchaTitle>Validá que no sos un robot:</CaptchaTitle>
{svg ?
<div dangerouslySetInnerHTML={{ __html: svg}} />
:
<span>Cargando imagen...</span>
}
<input type='text' maxlength='4' name="captcha" required value={this.state.captcha} onChange={this.captchaInput} />
</CaptchaGroup>
</Fragment>
}
<ErrorSpan>{this.state.formError}</ErrorSpan>
<ApoyarButton><img src={`${'/static/assets/apoyar-icon.svg'}`} />Quiero apoyar la propuesta</ApoyarButton>
</Fragment>
}
</Container>
)
}
}

export default WithUserContext(ApoyarFormulario)
23 changes: 15 additions & 8 deletions components/card/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import styled from 'styled-components'
import CardHeader from '../../elements/card-header/component'
import CardContent from '../../elements/card-content/component'
import CardSocial from '../../elements/card-social/component'
import WithDocumentTagsContext from '../../components/document-tags-context/component'

const CardContainer = styled.div`
margin: 0 1% 30px;
width: 31%;
width: 23%;
box-shadow: 0 4px 20px 0 rgba(0,0,0,0.05);
background-color: #ffffff;
border: solid 1px #e9e9e9;
Expand All @@ -17,28 +18,34 @@ box-sizing: border-box;
cursor: pointer;
display: block;
position: relative;
@media (max-width: 1100px) {
@media (max-width: 1408px) {
width: 31%;
}
@media (max-width: 1216px) {
width: 48%;
}
@media (max-width: 760px) {
@media (max-width: 600px) {
width: 100%;
}
`

const Card = ({ project }) => (
const Card = ({ project, tags }) => (
<CardContainer>
<Link href={{ pathname: '/propuesta', query: { id: project._id } }}>
<a>
<CardHeader img={project.currentVersion.content.imageCover} published={project.published} />
{/* <CardHeader img={project.currentVersion.content.imageCover} published={project.published} /> */}
<CardHeader hasImage={project.currentVersion.content.tags && project.currentVersion.content.tags.length > 0} img={`/static/assets/images/${tags && project.currentVersion.content.tags && project.currentVersion.content.tags.length > 0 ? tags.find(x => project.currentVersion.content.tags[0] == x.value).key : 'trama-default'}.jpg`} published={project.published} />
<CardContent
title={project.currentVersion.content.title}
authorId={project.author._id}
userId={project.author._id}
name={project.author.fullname}
hasImage={!!project.currentVersion.content.imageCover}
// hasImage={!!project.currentVersion.content.imageCover}
hasImage={project.currentVersion.content.tags && project.currentVersion.content.tags.length > 0}
party={project.author.fields && project.author.fields.party ? project.author.fields.party : ''} />
<CardSocial commentaries={project.commentsCount}
closed={project.closed} />
closed={project.closed}
apoyosCount={project.apoyosCount} />
</a>
</Link>
</CardContainer>
Expand All @@ -48,4 +55,4 @@ Card.propTypes = {
project: PropTypes.object.isRequired
}

export default Card
export default WithDocumentTagsContext(Card)
4 changes: 2 additions & 2 deletions components/project-fields/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -982,14 +982,14 @@ class ProjectFields extends Component {
onChange={this.handleInputChange}
placeholder='Hacer uso correcto de mayúsculas y minúsculas' />
</ProfileLabel>
<ProfileLabel>
{/* <ProfileLabel>
Ingrese la URL para la imagen de encabezado:
<InputField
type='text'
value={this.state.imageCover}
name='imageCover'
onChange={this.handleInputChange} />
</ProfileLabel>
</ProfileLabel> */}
<ProfileLabel>
Fecha de cierre del proyecto:
{/* <InputField
Expand Down
Loading

0 comments on commit b39138c

Please sign in to comment.