Skip to content

Commit

Permalink
✨ add/remove co-speaker (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpetetot committed Feb 21, 2018
1 parent 91c91d9 commit fc19388
Show file tree
Hide file tree
Showing 51 changed files with 656 additions and 107 deletions.
1 change: 1 addition & 0 deletions src/components/copyInput/copyInput.css
Expand Up @@ -7,6 +7,7 @@

.copy-input .copy-title {
white-space: nowrap;
color: var(--grey-color);
padding: 0 1em;
}

Expand Down
8 changes: 5 additions & 3 deletions src/components/copyInput/copyInput.jsx
Expand Up @@ -19,9 +19,11 @@ class CopyInput extends PureComponent {
const { title, value } = this.props
return (
<div className="copy-input">
<label htmlFor="copy-input" className="copy-title">
{title}
</label>
{title && (
<label htmlFor="copy-input" className="copy-title">
{title}
</label>
)}
<input
id="copy-input"
type="text"
Expand Down
1 change: 1 addition & 0 deletions src/components/form/inputButton/index.js
@@ -0,0 +1 @@
export { default } from './inputButton'
14 changes: 14 additions & 0 deletions src/components/form/inputButton/inputButton.css
@@ -0,0 +1,14 @@
.input-button {
display: flex;
}

.input-button > input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: transparent;
}

.input-button > button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
59 changes: 59 additions & 0 deletions src/components/form/inputButton/inputButton.jsx
@@ -0,0 +1,59 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import cn from 'classnames'

import './inputButton.css'

class InputButton extends Component {
state = { value: this.props.defaultValue }

handleChange = e => this.setState({ value: e.target.value })

handleClick = () => this.props.onClick(this.state.value)

handleKeyPress = (e) => {
if (e.charCode === 13) {
this.props.onClick(this.state.value)
}
}

render() {
const {
btnLabel, className, btnClassName, defaultValue, onClick, ...inputProps
} = this.props

return (
<div className={cn('input-button', className)}>
<input
defaultValue={this.state.value}
{...inputProps}
onChange={this.handleChange}
onKeyPress={this.handleKeyPress}
/>
<button
onClick={this.handleClick}
className={cn('btn', btnClassName)}
disabled={!this.state.value}
>
{btnLabel}
</button>
</div>
)
}
}

InputButton.propTypes = {
onClick: PropTypes.func.isRequired,
btnLabel: PropTypes.node.isRequired,
defaultValue: PropTypes.string,
className: PropTypes.string,
btnClassName: PropTypes.string,
}

InputButton.defaultProps = {
className: undefined,
defaultValue: undefined,
btnClassName: undefined,
}

export default InputButton
23 changes: 13 additions & 10 deletions src/components/modal/modal.css
Expand Up @@ -10,11 +10,14 @@
}

.modal-content {
position: relative;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
font-size: var(--font-size);
margin: 10% auto;
width: 70%;
width: 50%;
overflow: auto;
}

.modal-content .modal-close {
Expand All @@ -30,16 +33,16 @@
color: var(--primary-color);
}

@media only screen and (max-width: 480px) {
.modal {
background: none;

@media only screen and (max-width: 1024px) {
.modal-content {
width: 70%;
}
}

@media only screen and (max-width: 480px) {
.modal-content {
margin: 0;
margin-top: var(--layout-navbar-height);
height: 100%;
width: 100%;
height: calc(100% - var(--layout-navbar-height));
overflow: scroll;
}
}
13 changes: 5 additions & 8 deletions src/components/modal/withModal.js
@@ -1,9 +1,6 @@
/* eslint-disable react/jsx-filename-extension, react/prop-types */
import React from 'react'
import Modal from './modal.container'
import { inject } from '@k-ramel/react'

export default id => Component => ({ modalId, children, ...rest }) => (
<Modal id={id || modalId}>
<Component {...rest} />
</Modal>
)
export default id => Component => inject(store => ({
openModal: () => store.ui.modal.set({ openedModal: id }),
closeModal: () => store.ui.modal.set({ openedModal: undefined }),
}))(Component)
17 changes: 7 additions & 10 deletions src/firebase/proposals.js
Expand Up @@ -53,20 +53,18 @@ export const fetchEventProposals = async (eventId, uid, { categories, formats, s
/**
* Add a proposal to an event
* @param {string} eventId event id
* @param {object} talk talk data to copy
* @param {object} talkDataForEvent talk data for event
* @param {object} submittedTalk submittedTalk data
*/
export const addProposal = (eventId, talk, talkDataForEvent) => {
const { submissions, ...copyTalk } = talk
export const addProposal = (eventId, submittedTalk) => {
const { submissions, ...copyTalk } = submittedTalk
firebase
.firestore()
.collection('events')
.doc(eventId)
.collection('proposals')
.doc(talk.id)
.doc(submittedTalk.id)
.set({
...copyTalk,
...talkDataForEvent,
rating: null,
state: 'submitted',
updateTimestamp: firebase.firestore.FieldValue.serverTimestamp(),
Expand All @@ -88,17 +86,16 @@ export const removeProposal = async (eventId, talkId) => {
.delete()
}

export const updateProposal = (eventId, talk, talkDataForEvent) => {
const { submissions, ...copyTalk } = talk
export const updateProposal = (eventId, submittedTalk) => {
const { submissions, ...copyTalk } = submittedTalk
firebase
.firestore()
.collection('events')
.doc(eventId)
.collection('proposals')
.doc(talk.id)
.doc(submittedTalk.id)
.update({
...copyTalk,
...talkDataForEvent,
updateTimestamp: firebase.firestore.FieldValue.serverTimestamp(),
})
}
Expand Down
29 changes: 10 additions & 19 deletions src/firebase/submission.js
@@ -1,6 +1,6 @@
import firebase from 'firebase/app'
import { flow, unset } from 'immutadot'
import pick from 'lodash/pick'
import omit from 'lodash/omit'

import talksCrud from './talks'
import { updateProposal, addProposal, removeProposal } from './proposals'
Expand All @@ -15,28 +15,19 @@ export const saveTalkSubmission = async (talk, eventId, talkDataForEvent, isUpda
const db = firebase.firestore()
const batch = db.batch()

// add submission to talk and copy the submitted talk
talksCrud.update({
id: talk.id,
[`submissions.${eventId}`]: {
...talkDataForEvent,
...pick(talk, [
'title',
'description',
'abstract',
'references',
'level',
'speakers',
'updateTimestamp',
]),
},
})
const submittedTalk = {
...talkDataForEvent,
...omit(talk, ['createTimestamp', 'submissions']),
}

// add submission to talk
talksCrud.update({ id: talk.id, [`submissions.${eventId}`]: submittedTalk })

// add or update proposal to event
if (isUpdate) {
updateProposal(eventId, talk, talkDataForEvent)
updateProposal(eventId, submittedTalk)
} else {
addProposal(eventId, talk, talkDataForEvent)
addProposal(eventId, submittedTalk)
}
await batch.commit()
}
Expand Down
14 changes: 14 additions & 0 deletions src/firebase/user.js
@@ -1,3 +1,17 @@
import firebase from 'firebase/app'
import crud from './crud'

/**
* Fetch user by email
* @param {string} email user's email
*/
export const fetchUsersByEmail = async (email) => {
const result = await firebase
.firestore()
.collection('users')
.where('email', '==', email)
.get()
return result.docs.map(ref => ({ uid: ref.id, ...ref.data() }))
}

export default crud('users', 'uid')
11 changes: 9 additions & 2 deletions src/screens/components/speaker/speaker.jsx
Expand Up @@ -5,22 +5,29 @@ import Avatar from 'components/avatar'

import './speaker.css'

const Speaker = ({ displayName, photoURL, className }) => (
const Speaker = ({
displayName, photoURL, suffix, className,
}) => (
<div className={cn('speaker-avatar', className)}>
<Avatar displayName={displayName} photoURL={photoURL} />
<span className="speaker-avatar-fullname">{displayName}</span>
<span className="speaker-avatar-fullname">
{displayName}
{suffix && <span>&nbsp;{suffix}</span>}
</span>
</div>
)

Speaker.propTypes = {
displayName: PropTypes.string,
photoURL: PropTypes.string,
suffix: PropTypes.string,
className: PropTypes.string,
}

Speaker.defaultProps = {
displayName: undefined,
photoURL: undefined,
suffix: undefined,
className: undefined,
}

Expand Down
6 changes: 6 additions & 0 deletions src/screens/components/talk/abstract/abstract.css
@@ -1,3 +1,9 @@
.talk-abstract .talk-reference {
margin-top: 2em;
}

.talk-abstract .talk-abstract-title {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
12 changes: 10 additions & 2 deletions src/screens/components/talk/abstract/abstract.jsx
Expand Up @@ -2,12 +2,18 @@ import React from 'react'
import PropTypes from 'prop-types'
import cn from 'classnames'

import Badge from 'components/badge'
import Markdown from 'components/markdown'
import './abstract.css'

const TalkAbstract = ({ abstract, references, className }) => (
const TalkAbstract = ({
abstract, references, level, className,
}) => (
<div className={cn('talk-abstract card', className)}>
<h3>Abstract</h3>
<div className="talk-abstract-title">
<h3>Abstract</h3>
<Badge>Level {level}</Badge>
</div>
<Markdown source={abstract} />
<h3 className="talk-reference">References</h3>
<Markdown source={references} />
Expand All @@ -17,12 +23,14 @@ const TalkAbstract = ({ abstract, references, className }) => (
TalkAbstract.propTypes = {
abstract: PropTypes.string,
references: PropTypes.string,
level: PropTypes.string,
className: PropTypes.string,
}

TalkAbstract.defaultProps = {
abstract: undefined,
references: undefined,
level: 'not defined',
className: undefined,
}

Expand Down
@@ -0,0 +1,21 @@
a.add-speaker-button {
display: flex;
align-items: center;
text-decoration: none;
color: var(--grey-color-dark);
}

.add-speaker-button-icon {
border-radius: 50%;
height: 40px;
width: 40px;
background: var(--grey-color-light);
display: flex;
align-items: center;
justify-content: center;
}

.add-speaker-button-label {
margin-left: .8em;
text-decoration: underline;
}
@@ -0,0 +1,26 @@
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'

import { withModal } from 'components/modal'
import AddSpeakerModal from './addSpeakerModal.container'
import './addSpeakerButton.css'

const ADD_SPEAKER_MODAL = 'add-speaker-modal'

const AddSpeakerButton = ({ openModal }) => (
<Fragment>
<a onClick={openModal} role="button" className="add-speaker-button">
<span className="add-speaker-button-icon">
<i className="fa fa-user fa-lg" />
</span>
<span className="add-speaker-button-label">Add a co-speaker</span>
</a>
<AddSpeakerModal modalId={ADD_SPEAKER_MODAL} />
</Fragment>
)

AddSpeakerButton.propTypes = {
openModal: PropTypes.func.isRequired,
}

export default withModal(ADD_SPEAKER_MODAL)(AddSpeakerButton)

0 comments on commit fc19388

Please sign in to comment.