diff --git a/src/client/actions/teamSelectorActions.js b/src/client/actions/teamSelectorActions.js index ad6a3bec..65e3e237 100644 --- a/src/client/actions/teamSelectorActions.js +++ b/src/client/actions/teamSelectorActions.js @@ -1,4 +1,4 @@ -import { FETCH_TEAM_SELECTOR, FETCH_TEAM_SELECTOR_LIST, DELETE_TEAM_SELECTOR } from "./types"; +import { FETCH_TEAM_SELECTOR, FETCH_TEAM_SELECTOR_LIST, FETCH_TEAM_SELECTOR_CHOICES, DELETE_TEAM_SELECTOR } from "./types"; import { toast } from "react-toastify"; export const fetchTeamSelector = id => async (dispatch, getState, api) => { @@ -8,6 +8,13 @@ export const fetchTeamSelector = id => async (dispatch, getState, api) => { } }; +export const fetchTeamSelectorChoices = id => async (dispatch, getState, api) => { + const res = await api.get(`/teamSelectors/${id}/choices`); + if (res.data) { + dispatch({ type: FETCH_TEAM_SELECTOR_CHOICES, payload: { id, choices: res.data } }); + } +}; + export const fetchTeamSelectorForGame = id => async (dispatch, getState, api) => { const res = await api.get(`/teamSelectors/game/${id}`); dispatch({ type: FETCH_TEAM_SELECTOR, payload: res.data }); diff --git a/src/client/actions/types.js b/src/client/actions/types.js index f0195d1a..843d2b53 100644 --- a/src/client/actions/types.js +++ b/src/client/actions/types.js @@ -103,6 +103,7 @@ export const SEND_ERROR = "send_error"; //Team Selectors export const FETCH_TEAM_SELECTOR_LIST = "fetch_team_selector_list"; export const FETCH_TEAM_SELECTOR = "fetch_team_selector"; +export const FETCH_TEAM_SELECTOR_CHOICES = "fetch_team_selector_choices"; export const DELETE_TEAM_SELECTOR = "delete_team_selector"; //OAuth diff --git a/src/client/components/social/ShareDialog.js b/src/client/components/social/ShareDialog.js index 66a1cfe1..37875610 100644 --- a/src/client/components/social/ShareDialog.js +++ b/src/client/components/social/ShareDialog.js @@ -129,15 +129,7 @@ class ShareDialog extends Component { } renderDialog() { - const { - authorisedAccounts, - fetchingPreview, - images, - isSubmitting, - service, - services, - submittedPost - } = this.state; + const { authorisedAccounts, fetchingPreview, images, isSubmitting, service, services, submittedPost } = this.state; if (service) { let content; @@ -153,11 +145,7 @@ class ShareDialog extends Component { content = (
{this.renderImagePreview()} -
@@ -176,12 +164,7 @@ class ShareDialog extends Component { let url, link; switch (service) { case "twitter": { - url = [ - "https://twitter.com", - authorisedAccounts[service].screen_name, - "status", - submittedPost.id - ].join("/"); + url = ["https://twitter.com", authorisedAccounts[service].screen_name, "status", submittedPost.id].join("/"); } } @@ -245,13 +228,12 @@ class ShareDialog extends Component { includeDisclaimer = true; content = [

- To share to Twitter, you will need to click the button below and authorise posts from this - website. Granting authorisation simply allows you to post to your own account - from this website. + To share to Twitter, you will need to click the button below and authorise posts from this website. Granting authorisation simply allows{" "} + you to post to your own account from this website.

,

- No personal data is ever saved by us, and your password is never exposed by Twitter, meaning it - is impossible for us (or anyone else) to access your account or post without your consent. + No personal data is ever saved by us, and your password is never exposed by Twitter, meaning it is impossible for us (or anyone else) to + access your account or post without your consent.

]; break; @@ -297,25 +279,18 @@ class ShareDialog extends Component {
Sharing to Twitter

- Upon clicking the Share button, a Twitter window will pop up asking you to grant access - to {site_name} (the website, not the page/group). If you accept, an access token will be - saved to your web browser. + Upon clicking the Share button, a Twitter window will pop up asking you to grant access to {site_name} (the website, not the + page/group). If you accept, an access token will be saved to your web browser.

- This access token is a long string of letters and numbers, generated by Twitter to allow - other apps to post to your account without accessing twitter.com directly. It is - accessible only within your web browser, so at no point can this be accessed or used by - the 4Fs team (or anyone else). + This access token is a long string of letters and numbers, generated by Twitter to allow other apps to post to your account + without accessing twitter.com directly. It is accessible only within your web browser, so at no point can this be accessed or + used by the 4Fs team (or anyone else).

- The access token can easily be deleted, if you wish, either by clicking the{" "} - Disconnect link above the Tweet editor, or by revoking it directly on - Twitter. If you have any more questions,{" "} - + The access token can easily be deleted, if you wish, either by clicking the Disconnect link above the Tweet + editor, or by revoking it directly on Twitter. If you have any more questions,{" "} + just let us know

@@ -325,9 +300,7 @@ class ShareDialog extends Component { } if (content) { - return ( - this.setState({ isShowingDisclaimer: false })}>{content} - ); + return this.setState({ isShowingDisclaimer: false })}>{content}; } } } @@ -353,9 +326,7 @@ class ShareDialog extends Component { //Firefox disables twitter images by default via its //tracker. Easier to just hide it if (browser !== "Firefox") { - userImage = ( - Profile Picture - ); + userImage = Profile Picture; } userInfo = (
@@ -383,12 +354,9 @@ class ShareDialog extends Component { renderImagePreview() { const { onFetchImage } = this.props; - console.log("HELLO"); const { images, fetchingPreview } = this.state; if (images.length) { - const list = images.map((src, i) => ( - window.open(src)} alt="Preview" /> - )); + const list = images.map((src, i) => window.open(src)} alt="Preview" />); return
{list}
; } else if (fetchingPreview) { return ; diff --git a/src/client/components/teamselectors/ShareableTeamSelector.js b/src/client/components/teamselectors/ShareableTeamSelector.js index c3c8c5f1..44156e7a 100644 --- a/src/client/components/teamselectors/ShareableTeamSelector.js +++ b/src/client/components/teamselectors/ShareableTeamSelector.js @@ -10,7 +10,7 @@ import ShareDialog from "../social/ShareDialog"; import LoadingPage from "../LoadingPage"; //Actions -import { fetchPreviewImage, saveTeamSelectorChoices, shareTeamSelector } from "~/client/actions/teamSelectorActions"; +import { fetchPreviewImage, saveTeamSelectorChoices, shareTeamSelector, fetchTeamSelectorChoices } from "~/client/actions/teamSelectorActions"; import { fetchTeam } from "~/client/actions/teamsActions"; class ShareableTeamSelector extends Component { @@ -21,7 +21,7 @@ class ShareableTeamSelector extends Component { } static getDerivedStateFromProps(nextProps, prevState) { - const { fetchTeam, fullTeams, selector } = nextProps; + const { fetchTeam, fullTeams, selector, fetchTeamSelectorChoices } = nextProps; const newState = { selector }; //Ensure we have all dependencies @@ -48,14 +48,19 @@ class ShareableTeamSelector extends Component { //from the selector, and then revisits the page), we //enforce edit mode const { activeUserChoices, players } = newState.selector; - if (activeUserChoices) { + if (activeUserChoices != null) { const missingPlayers = _.difference( activeUserChoices, players.map(p => p._id) ); + if (missingPlayers.length) { - newState.editMode = true; + newState.enforceEditMode = true; } + newState.isLoadingChoices = false; + } else if (!prevState.isLoadingChoices) { + fetchTeamSelectorChoices(newState.selector._id); + newState.isLoadingChoices = true; } return newState; @@ -67,30 +72,35 @@ class ShareableTeamSelector extends Component { saveTeamSelectorChoices(selector._id, values); - this.setState({ editMode: false }); + this.setState({ enforceEditMode: false }); + } + + selectionIsComplete() { + const { enforceEditMode, selector } = this.state; + return !enforceEditMode && selector.activeUserChoices != null && selector.activeUserChoices.length > 0; } renderSecondColumn() { - const { baseUrl, fetchPreviewImage, shareTeamSelector, site_social, urlFormatter } = this.props; - const { editMode, selector } = this.state; + if (this.selectionIsComplete()) { + const { baseUrl, fetchPreviewImage, shareTeamSelector, site_social, urlFormatter } = this.props; + const { selector } = this.state; - //Get Initial Share Values - let initialContent = selector.defaultSocialText || ""; + //Get Initial Share Values + let initialContent = selector.defaultSocialText || ""; - //Get Tokens - const url = `${baseUrl}/${urlFormatter(selector)}`; + //Get Tokens + const url = `${baseUrl}/${urlFormatter(selector)}`; - //Replace tokens - initialContent = initialContent.replace(/{url}/gi, url).replace(/@*{site_social}/gi, "@" + site_social); + //Replace tokens + initialContent = initialContent.replace(/{url}/gi, url).replace(/@*{site_social}/gi, "@" + site_social); - if (selector.activeUserChoices && !editMode) { return (

Thank you, your choices have been saved!

{"Want to make a change? "} - this.setState({ editMode: true })}> + this.setState({ enforceEditMode: true })}> Click Here {" to edit your team"} @@ -109,9 +119,9 @@ class ShareableTeamSelector extends Component { render() { const { fullTeams, localTeam } = this.props; - const { editMode, isLoadingTeam, selector, squadNumbers } = this.state; + const { isLoadingTeam, isLoadingChoices, selector, squadNumbers } = this.state; - if (isLoadingTeam) { + if (isLoadingTeam || isLoadingChoices) { return ; } @@ -129,7 +139,7 @@ class ShareableTeamSelector extends Component { //Get current squad let currentSquad = {}; - if (selector.activeUserChoices) { + if (selector.activeUserChoices && selector.activeUserChoices.length) { currentSquad = _.chain(selector.activeUserChoices) .map((_player, i) => { //Get Position @@ -156,7 +166,7 @@ class ShareableTeamSelector extends Component { players={players} requireFullTeam={true} submitText="Save Choices" - readOnly={selector.activeUserChoices && !editMode} + readOnly={this.selectionIsComplete()} team={fullTeams[localTeam]} usesExtraInterchange={selector.usesExtraInterchange} /> @@ -175,14 +185,16 @@ ShareableTeamSelector.defaultProps = { urlFormatter: s => `team-selectors/${s.slug}` }; -function mapStateToProps({ config, teams }) { +function mapStateToProps({ config, teams, teamSelectors }) { const { baseUrl, localTeam, site_social } = config; const { fullTeams } = teams; - return { baseUrl, fullTeams, localTeam, site_social }; + const { selectors } = teamSelectors; + return { baseUrl, fullTeams, localTeam, site_social, selectors }; } export default connect(mapStateToProps, { fetchPreviewImage, + fetchTeamSelectorChoices, saveTeamSelectorChoices, shareTeamSelector, fetchTeam diff --git a/src/client/reducers/teamSelectorReducer.js b/src/client/reducers/teamSelectorReducer.js index 2cfa2f41..e12a428d 100644 --- a/src/client/reducers/teamSelectorReducer.js +++ b/src/client/reducers/teamSelectorReducer.js @@ -1,9 +1,9 @@ import _ from "lodash"; -import { FETCH_TEAM_SELECTOR, FETCH_TEAM_SELECTOR_LIST, DELETE_TEAM_SELECTOR } from "../actions/types"; +import { FETCH_TEAM_SELECTOR, FETCH_TEAM_SELECTOR_LIST, DELETE_TEAM_SELECTOR, FETCH_TEAM_SELECTOR_CHOICES } from "../actions/types"; import { teamSelectors as listProperties } from "~/constants/listProperties"; -export default function (state = { selectors: {}, haveLoadedAll: false }, action) { +export default function(state = { selectors: {}, haveLoadedAll: false }, action) { switch (action.type) { case FETCH_TEAM_SELECTOR: { return { @@ -18,6 +18,18 @@ export default function (state = { selectors: {}, haveLoadedAll: false }, action } }; } + case FETCH_TEAM_SELECTOR_CHOICES: { + return { + ...state, + selectors: { + ...state.selectors, + [action.payload.id]: { + ...state.selectors[action.payload.id], + activeUserChoices: action.payload.choices + } + } + }; + } case FETCH_TEAM_SELECTOR_LIST: { return { diff --git a/src/controllers/teamSelectorController.js b/src/controllers/teamSelectorController.js index 71dd6c9f..755dbc09 100644 --- a/src/controllers/teamSelectorController.js +++ b/src/controllers/teamSelectorController.js @@ -59,13 +59,7 @@ export async function getTeamSelector(req, res) { const selector = await fetchTeamSelector(_id, res); if (selector) { - //Set choices for active user - const activeUserChoices = selector.choices.find(({ ip }) => ip == req.ipAddress); - if (activeUserChoices) { - selector.activeUserChoices = activeUserChoices.squad; - } - - //Remove other choices for non-admins + //Remove choices for non-admins if (!req.user || !req.user.isAdmin) { delete selector.choices; } @@ -74,6 +68,17 @@ export async function getTeamSelector(req, res) { } } +export async function getTeamSelectorChoicesByIp(req, res) { + // We do this separately to the main call as IP detection just doesn't work with SSR. + const { _id } = req.params; + + const selector = await fetchTeamSelector(_id, res); + if (selector) { + const activeUserChoices = selector.choices.find(({ ip }) => ip == req.ipAddress); + res.send(activeUserChoices ? activeUserChoices.squad : []); + } +} + export async function getPreviewImage(req, res) { const { _id } = req.params; const selector = await fetchTeamSelector(_id, res); @@ -190,10 +195,7 @@ export async function submitUserChoices(req, res) { const squad = _.values(req.body); if (currentVote) { - await TeamSelector.updateOne( - { _id, "choices._id": currentVote._id }, - { $set: { "choices.$.squad": squad } } - ); + await TeamSelector.updateOne({ _id, "choices._id": currentVote._id }, { $set: { "choices.$.squad": squad } }); } else { selector.choices.push({ ip: ipAddress, squad }); await selector.save(); @@ -243,10 +245,7 @@ export async function generateImage(req, res, selector) { const { squad } = activeUserChoices; //Get main player info - const playerData = await Person.find( - { _id: { $in: squad } }, - "name images nickname displayNicknameInCanvases squadNameWhenDuplicate gender" - ).lean(); + const playerData = await Person.find({ _id: { $in: squad } }, "name images nickname displayNicknameInCanvases squadNameWhenDuplicate gender").lean(); //Get squad numbers let squadNumbers = []; diff --git a/src/images/SquadImage.js b/src/images/SquadImage.js index 8224ebe6..6f641839 100644 --- a/src/images/SquadImage.js +++ b/src/images/SquadImage.js @@ -285,7 +285,7 @@ export default class SquadImage extends Canvas { } this.textBuilder(bannerText, sideBarWidth * 0.5, bannerY, { - lineHeight: 2.8 + lineHeight: 2.9 }); //Interchanges Header, for normal interchange display diff --git a/src/routes/teamSelectorRoutes.js b/src/routes/teamSelectorRoutes.js index 39626873..54e56e73 100644 --- a/src/routes/teamSelectorRoutes.js +++ b/src/routes/teamSelectorRoutes.js @@ -7,6 +7,7 @@ import requireAdmin from "~/middlewares/requireAdmin"; export default app => { //Getters app.get("/api/teamSelectors/game/:_game", teamSelectorController.getTeamSelectorForGame); + app.get("/api/teamSelectors/:_id/choices", teamSelectorController.getTeamSelectorChoicesByIp); app.get("/api/teamSelectors/:_id/previewImage", teamSelectorController.getPreviewImage); app.get("/api/teamSelectors/:_id", teamSelectorController.getTeamSelector); app.get("/api/teamSelectors/", teamSelectorController.getAllTeamSelectors);