Skip to content

Commit

Permalink
Add option to make newdle summary public/private
Browse files Browse the repository at this point in the history
Participants can now look at the summary of newdles if it is set to
public by the creator.
  • Loading branch information
Leats committed Aug 20, 2020
1 parent 69bd24f commit f56c375
Show file tree
Hide file tree
Showing 19 changed files with 300 additions and 59 deletions.
1 change: 1 addition & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def dummy_newdle(db_session, dummy_uid):
creator_uid=dummy_uid,
creator_name='Dummy',
duration=timedelta(minutes=60),
private=True,
timezone='Europe/Zurich',
timeslots=[
datetime(2019, 9, 11, 13, 0),
Expand Down
6 changes: 4 additions & 2 deletions newdle/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def get_newdles_participating():

@api.route('/newdle/', methods=('POST',))
@use_kwargs(NewNewdleSchema(), locations=('json',))
def create_newdle(title, duration, timezone, timeslots, participants):
def create_newdle(title, duration, timezone, timeslots, participants, private):
newdle = Newdle(
title=title,
creator_uid=g.user['uid'],
Expand All @@ -241,6 +241,7 @@ def create_newdle(title, duration, timezone, timeslots, participants):
timezone=timezone,
timeslots=timeslots,
participants={Participant(**p) for p in participants},
private=private,
)
db.session.add(newdle)
db.session.commit()
Expand Down Expand Up @@ -284,11 +285,12 @@ def update_newdle(args, code):


@api.route('/newdle/<code>/participants/')
@allow_anonymous
def get_participants(code):
newdle = Newdle.query.filter_by(code=code).first_or_404(
'Specified newdle does not exist'
)
if newdle.creator_uid != g.user['uid']:
if newdle.private and (g.user is None or newdle.creator_uid != g.user['uid']):
raise Forbidden('You cannot view the participants of this newdle')
return RestrictedParticipantSchema(many=True).jsonify(newdle.participants)

Expand Down
5 changes: 5 additions & 0 deletions newdle/client/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const SET_DURATION = 'Set meeting duration';
export const ADD_TIMESLOT = 'Add new timeslot';
export const REMOVE_TIMESLOT = 'Remove a timeslot';
export const SET_TITLE = 'Set title for Newdle';
export const SET_PRIVATE = 'Keep list of participants private';
export const SET_TIMEZONE = 'Set the meeting timezone';
export const NEWDLE_RECEIVED = 'Received newdle data';
export const CLEAR_NEWDLE = 'Clear newdle data';
Expand Down Expand Up @@ -141,6 +142,10 @@ export function setTitle(title) {
return {type: SET_TITLE, title};
}

export function setPrivate(isPrivate) {
return {type: SET_PRIVATE, private: isPrivate};
}

export function fetchNewdle(code, fullDetails = false, action = NEWDLE_RECEIVED) {
return async dispatch => {
const newdle = await client.catchErrors(client.getNewdle(code));
Expand Down
15 changes: 11 additions & 4 deletions newdle/client/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,23 @@ class Client {
return this._request(flask`api.users`({name, email}));
}

createNewdle(title, duration, timezone, timeslots, participants) {
createNewdle(title, duration, timezone, timeslots, participants, isPrivate) {
const params = {
method: 'POST',
body: JSON.stringify({title, duration, timezone, timeslots, participants}),
body: JSON.stringify({
title,
duration,
timezone,
timeslots,
participants,
private: isPrivate,
}),
};
return this._request(flask`api.create_newdle`(), params);
}

getNewdle(code) {
return this._request(flask`api.get_newdle`({code}), {anonymous: true});
return this._request(flask`api.get_newdle`({code}), {anonymous: !this.token});
}

getMyNewdles() {
Expand Down Expand Up @@ -169,7 +176,7 @@ class Client {
}

getParticipants(code) {
return this._request(flask`api.get_participants`({code}));
return this._request(flask`api.get_participants`({code}), {anonymous: !this.token});
}

getParticipant(newdleCode, participantCode) {
Expand Down
63 changes: 53 additions & 10 deletions newdle/client/src/components/NewdleTitle.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,72 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Container, Label} from 'semantic-ui-react';
import {useSelector} from 'react-redux';
import {useHistory} from 'react-router';
import {useRouteMatch} from 'react-router-dom';
import {Container, Icon, Button, Popup} from 'semantic-ui-react';
import {getUserInfo} from '../selectors';
import styles from './NewdleTitle.module.scss';

export default function NewdleTitle({title, author, label, finished}) {
export default function NewdleTitle({title, author, creatorUid, finished, code, isPrivate}) {
const userInfo = useSelector(getUserInfo);
const history = useHistory();
const isSummary = !!useRouteMatch({path: '/newdle/:code/summary'});

return (
<Container text className={styles.box}>
<div className={styles.title}>
<h1 className={styles.header}>{title}</h1>
{label && (
<Label basic color={finished ? 'blue' : 'green'} size="tiny" className={styles.label}>
{label}
</Label>
<Container text className={styles['box']}>
<div className={styles['flexbox']}>
<div>
<div className={styles['title']}>
<h1 className={styles['header']}>{title}</h1>
</div>
<div className={styles['subtitle']}>by {author}</div>
</div>
{(!isPrivate || (userInfo && userInfo.uid === creatorUid)) && (
<div className={styles['view-options']}>
<Button.Group>
<Popup
content={!finished ? 'Answer newdle' : 'This newdle has already finished'}
position="bottom center"
trigger={
<Button
icon
active={!isSummary}
onClick={() => (isSummary ? history.push(`/newdle/${code}/`) : null)}
disabled={finished}
>
<Icon name="calendar plus outline" />
</Button>
}
/>
<Popup
content="View summary"
position="bottom center"
trigger={
<Button
icon
active={isSummary}
onClick={() => (!isSummary ? history.push(`/newdle/${code}/summary`) : null)}
>
<Icon name="tasks" />
</Button>
}
/>
</Button.Group>
</div>
)}
</div>
<div className={styles.subtitle}>by {author}</div>
</Container>
);
}

NewdleTitle.propTypes = {
title: PropTypes.string.isRequired,
author: PropTypes.string.isRequired,
creatorUid: PropTypes.string.isRequired,
label: PropTypes.string,
finished: PropTypes.bool,
code: PropTypes.string.isRequired,
isPrivate: PropTypes.bool.isRequired,
};

NewdleTitle.defaultProps = {
Expand Down
11 changes: 11 additions & 0 deletions newdle/client/src/components/NewdleTitle.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,15 @@
.box {
padding: 15px 1.5em;
background-color: $white;

.flexbox {
display: flex;
flex-direction: row;
justify-content: space-between;
}

.view-options {
display: flex;
align-items: center;
}
}
20 changes: 15 additions & 5 deletions newdle/client/src/components/ParticipantTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function AvailabilityRow({
setActiveDate,
active,
finalized,
isCreator,
children,
}) {
const numberOfParticipants = useSelector(getNumberOfParticipants);
Expand All @@ -75,12 +76,12 @@ function AvailabilityRow({
return (
<Table.Row
className={
finalized
finalized || !isCreator
? `${styles['participant-row']} ${styles['finalized']}`
: styles['participant-row']
}
style={!finalized || active ? null : {opacity: '0.3'}}
onClick={() => (finalized ? null : setActiveDate(startDt))}
onClick={() => (finalized || !isCreator ? null : setActiveDate(startDt))}
active={active}
>
<Table.Cell width={3}>
Expand Down Expand Up @@ -114,7 +115,7 @@ function AvailabilityRow({
</div>
{active && children}
</Table.Cell>
{!finalized && (
{!finalized && isCreator && (
<Table.Cell width={1} textAlign="right">
<Radio name="slot-id" value={startDt} checked={active} />
</Table.Cell>
Expand All @@ -141,14 +142,21 @@ AvailabilityRow.propTypes = {
setActiveDate: PropTypes.func.isRequired,
active: PropTypes.bool.isRequired,
finalized: PropTypes.bool.isRequired,
isCreator: PropTypes.bool.isRequired,
children: PropTypes.node,
};

AvailabilityRow.defaultProps = {
children: null,
};

export default function ParticipantTable({finalDate, setFinalDate, finalized, children}) {
export default function ParticipantTable({
finalDate,
setFinalDate,
finalized,
isCreator,
children,
}) {
const availabilityData = useSelector(getParticipantAvailability);

if (availabilityData.length === 0) {
Expand All @@ -157,7 +165,7 @@ export default function ParticipantTable({finalDate, setFinalDate, finalized, ch

return (
<div className={styles['participant-table']}>
<Table textAlign="center" definition selectable={!finalized}>
<Table textAlign="center" definition selectable={!finalized && isCreator}>
<Table.Body>
{availabilityData.map(availability => (
<AvailabilityRow
Expand All @@ -166,6 +174,7 @@ export default function ParticipantTable({finalDate, setFinalDate, finalized, ch
setActiveDate={setFinalDate}
active={availability.startDt === finalDate}
finalized={finalized}
isCreator={isCreator}
>
{children}
</AvailabilityRow>
Expand All @@ -180,6 +189,7 @@ ParticipantTable.propTypes = {
finalDate: PropTypes.string,
setFinalDate: PropTypes.func.isRequired,
finalized: PropTypes.bool.isRequired,
isCreator: PropTypes.bool.isRequired,
children: PropTypes.node,
};

Expand Down
11 changes: 10 additions & 1 deletion newdle/client/src/components/answer/AnswerHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ export default function AnswerHeader({match}) {
return null;
}

return <NewdleTitle title={newdle.title} author={newdle.creator_name} />;
return (
<NewdleTitle
title={newdle.title}
author={newdle.creator_name}
creatorUid={newdle.creator_uid}
finished={!!newdle.final_dt}
code={newdle.code}
isPrivate={newdle.private}
/>
);
}

AnswerHeader.propTypes = {
Expand Down
2 changes: 1 addition & 1 deletion newdle/client/src/components/answer/AnswerPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default function AnswerPage() {
}, [newdle, user, participantCode, dispatch, submitting]);

useEffect(() => {
if (user && !participantCode && participant) {
if (newdle && user && !participantCode && participant) {
history.replace(`/newdle/${newdle.code}/${participant.code}`);
}
}, [newdle, user, participant, history, participantCode]);
Expand Down
41 changes: 37 additions & 4 deletions newdle/client/src/components/creation/FinalStep.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
import React from 'react';
import React, {useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {useHistory} from 'react-router';
import {Button, Container, Header, Input} from 'semantic-ui-react';
import {Button, Container, Header, Icon, Input, Checkbox} from 'semantic-ui-react';
import {
getDuration,
getFullTimeslots,
getParticipantData,
getTimezone,
getTitle,
getPrivacySetting,
} from '../../selectors';
import client from '../../client';
import {newdleCreated, setStep, setTitle} from '../../actions';
import {newdleCreated, setStep, setTitle, setPrivate} from '../../actions';
import styles from './creation.module.scss';

export default function FinalStep() {
const title = useSelector(getTitle);
const isPrivate = useSelector(getPrivacySetting);
const duration = useSelector(getDuration);
const timeslots = useSelector(getFullTimeslots);
const participants = useSelector(getParticipantData);
const timezone = useSelector(getTimezone);
const dispatch = useDispatch();
const history = useHistory();
const [_createNewdle, submitting] = client.useBackendLazy(client.createNewdle);
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);

async function createNewdle() {
const newdle = await _createNewdle(title, duration, timezone, timeslots, participants);
const newdle = await _createNewdle(
title,
duration,
timezone,
timeslots,
participants,
isPrivate
);

if (newdle) {
dispatch(newdleCreated(newdle));
Expand Down Expand Up @@ -68,6 +78,29 @@ export default function FinalStep() {
</p>
)}
</div>
<div className={styles['advanced-options']}>
<div
className={styles['headerbar']}
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
>
<Header as="h3" className={styles['header']}>
Advanced options
</Header>
<Icon name={showAdvancedOptions ? 'chevron up' : 'chevron down'} />
</div>
{showAdvancedOptions && (
<div className={styles['options']}>
<label htmlFor="togglePrivate">Keep list of participants private</label>
<Checkbox
id="togglePrivate"
toggle
checked={isPrivate}
disabled={submitting}
onChange={(_, {checked}) => dispatch(setPrivate(checked))}
/>
</div>
)}
</div>
<div className={styles['create-button']}>
<Button
color="violet"
Expand Down
Loading

0 comments on commit f56c375

Please sign in to comment.