-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate tribe's JoinButton to React (#1123)
as seen on /tribes page
- Loading branch information
mrkvon
authored
Dec 13, 2019
1 parent
87fd73a
commit bc65dff
Showing
5 changed files
with
183 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import axios from 'axios'; | ||
|
||
export async function join(tribeId) { | ||
const { data } = await axios.post(`/api/users/memberships/${tribeId}`); | ||
return data; | ||
} | ||
|
||
export async function leave(tribeId) { | ||
const { data } = await axios.delete(`/api/users/memberships/${tribeId}`); | ||
return data; | ||
} |
120 changes: 120 additions & 0 deletions
120
modules/tribes/client/components/JoinButton.component.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import React, { useState } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { useTranslation } from 'react-i18next'; | ||
import LeaveTribeModal from './LeaveTribeModal'; | ||
import { OverlayTrigger, Tooltip } from 'react-bootstrap'; | ||
|
||
import * as api from '../api/tribes.api'; | ||
|
||
function JoinButtonPresentational({ isMember, isLoading, joinLabel='Join', joinedLabel='Joined', tribe, isLoggedIn, onToggle }) { | ||
const { t } = useTranslation('tribes'); | ||
|
||
const ariaLabel = (isMember) ? t('Leave Tribe') : t(`${joinLabel} ({{label}})`, { label: tribe.label }); | ||
const buttonLabel = (isMember) ? t(joinedLabel) : t(joinLabel); | ||
|
||
// a button to be shown when user is signed out | ||
if (!isLoggedIn) { | ||
return <a | ||
href={`/signup?tribe=${tribe.slug}`} | ||
type="button" | ||
className="btn btn-sm btn-default tribe-join" | ||
> | ||
<i className="icon-plus" /> {buttonLabel} | ||
</a>; | ||
} | ||
|
||
// a button for joining and leaving a tribe | ||
const leaveTooltip = <Tooltip id={`tribe-${tribe._id}`} placement="bottom">{t('Leave Tribe')}</Tooltip>; | ||
const btn = <button | ||
type="button" | ||
className={`${isMember ? 'btn-active' : ''} btn btn-sm btn-default tribe-join`} | ||
onClick={onToggle} | ||
disabled={isLoading} | ||
aria-label={ariaLabel} | ||
> | ||
<i className={(isMember) ? 'icon-ok' : 'icon-plus'} /> {buttonLabel} | ||
</button>; | ||
|
||
if (isMember) { | ||
return <OverlayTrigger placement="bottom" overlay={leaveTooltip}>{btn}</OverlayTrigger>; | ||
} else { | ||
return btn; | ||
} | ||
} | ||
|
||
JoinButtonPresentational.propTypes = { | ||
isMember: PropTypes.bool.isRequired, | ||
isLoading: PropTypes.bool, | ||
isLoggedIn: PropTypes.bool.isRequired, | ||
joinLabel: PropTypes.string, | ||
joinedLabel: PropTypes.string, | ||
tribe: PropTypes.object.isRequired, | ||
onToggle: PropTypes.func | ||
}; | ||
|
||
// @TODO this can (and should) be replaced by other container, when we finish the migration; when we start using redux etc. | ||
export default function JoinButton({ tribe, user, onUpdated, ...rest }) { | ||
// isLeaving controls whether the modal for leaving a tribe is shown | ||
const [isLeaving, setIsLeaving] = useState(false); | ||
|
||
const [isUpdating, setIsUpdating] = useState(false); | ||
|
||
const isMemberInitial = user && user.memberIds && user.memberIds.indexOf(tribe._id) > -1; | ||
const [isMember, setIsMember] = useState(isMemberInitial); | ||
|
||
/** | ||
* Handle joining or leaving of a tribe | ||
* - Join: join tribe immediately (api call) | ||
* - Leave: show confirmation modal | ||
*/ | ||
async function handleToggleMembership() { | ||
if (isUpdating) { | ||
return; | ||
} | ||
|
||
if (isMember) { | ||
setIsLeaving(true); | ||
} else { | ||
// updating starts | ||
setIsUpdating(true); | ||
|
||
// join | ||
const data = await api.join(tribe._id); | ||
// update the membership locally | ||
setIsMember(true); | ||
|
||
// updating finished | ||
setIsUpdating(false); | ||
// tell the ancestor components that the membership was updated | ||
onUpdated(data); | ||
} | ||
} | ||
|
||
/** | ||
* Leave a tribe (api call) | ||
*/ | ||
async function handleLeave() { | ||
setIsUpdating(true); | ||
const data = await api.leave(tribe._id); | ||
setIsUpdating(false); | ||
setIsLeaving(false); | ||
setIsMember(false); | ||
// tell the ancestor components that the membership was updated | ||
onUpdated(data); | ||
} | ||
|
||
function handleCancelLeave() { | ||
setIsLeaving(false); | ||
} | ||
|
||
return <> | ||
<LeaveTribeModal show={isLeaving} tribe={tribe} onConfirm={handleLeave} onCancel={handleCancelLeave}/> | ||
<JoinButtonPresentational tribe={tribe} isLoggedIn={!!user} isMember={isMember} isLoading={isUpdating} {...rest} onToggle={handleToggleMembership} /> | ||
</>; | ||
} | ||
|
||
JoinButton.propTypes = { | ||
tribe: PropTypes.object.isRequired, | ||
user: PropTypes.object, | ||
onUpdated: PropTypes.func.isRequired | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { Modal } from 'react-bootstrap'; | ||
|
||
export default function LeaveTribeModal({ tribe, show, onConfirm, onCancel }) { | ||
|
||
const { t } = useTranslation('tribes'); | ||
|
||
return ( | ||
<Modal show={show} onHide={onCancel}> | ||
<div className="modal-content"> | ||
<Modal.Header> | ||
<Modal.Title>{t('Leave this Tribe?')}</Modal.Title> | ||
</Modal.Header> | ||
|
||
<Modal.Body> | ||
{t('Do you want to leave "{{label}}"?', { label: tribe.label })} | ||
</Modal.Body> | ||
|
||
<Modal.Footer> | ||
<button className="btn btn-primary" onClick={onConfirm}>{t('Leave Tribe')}</button> | ||
<button className="btn btn-default" onClick={onCancel}>{t('Cancel')}</button> | ||
</Modal.Footer> | ||
</div> | ||
</Modal> | ||
); | ||
} | ||
|
||
LeaveTribeModal.propTypes = { | ||
show: PropTypes.bool.isRequired, | ||
onConfirm: PropTypes.func.isRequired, | ||
onCancel: PropTypes.func.isRequired, | ||
tribe: PropTypes.object.isRequired | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters