diff --git a/web-ui/src/pages/FeedbackRequestPage.jsx b/web-ui/src/pages/FeedbackRequestPage.jsx index e8f840b24e..7692f4e14e 100644 --- a/web-ui/src/pages/FeedbackRequestPage.jsx +++ b/web-ui/src/pages/FeedbackRequestPage.jsx @@ -1,4 +1,4 @@ -import React, {useContext, useCallback, useEffect, useState} from "react"; +import React, {useContext, useCallback, useEffect, useState, useRef} from "react"; import { makeStyles } from "@material-ui/core/styles"; import Stepper from "@material-ui/core/Stepper"; import Step from "@material-ui/core/Step"; @@ -78,23 +78,14 @@ const FeedbackRequestPage = () => { const currentUserId = memberProfile?.id; const location = useLocation(); const history = useHistory(); - const [query, setQuery] = useState({}); - const stepQuery = query.step?.toString(); - const templateQuery = query.template?.toString(); - const fromQuery = query.from ? query.from : []; - const sendQuery = query.send?.toString(); - const dueQuery = query.due?.toString(); - const sendDate = query.send?.toString(); - const forQuery = query.for?.toString(); - const requestee = selectProfile(state, forQuery); - const memberIds = selectCurrentMemberIds(state); const csrf = selectCsrfToken(state); + const [query, setQuery] = useState({}); + const queryLoaded = useRef(false); const [readyToProceed, setReadyToProceed] = useState(false); const [templateIsValid, setTemplateIsValid] = useState(); - - useEffect(() => { - setQuery(queryString.parse(location?.search)); - }, [location.search]); + const [requestee, setRequestee] = useState({}); + const [memberIds, setMemberIds] = useState([]); + const [activeStep, setActiveStep] = useState(1); const handleQueryChange = useCallback((key, value) => { let newQuery = { @@ -105,51 +96,18 @@ const FeedbackRequestPage = () => { }, [history, location, query]); const getStep = useCallback(() => { - if (!stepQuery || stepQuery < 1 || !/^\d+$/.test(stepQuery)) + if (!query.step || query.step < 1 || !/^\d+$/.test(query.step)) return 1; - else return parseInt(stepQuery); - }, [stepQuery]); - - const activeStep = getStep(); + else return parseInt(query.step); + }, [query.step]); const hasFor = useCallback(() => { - return !!forQuery; - }, [forQuery]) - - useEffect(() => { - async function isTemplateValid() { - if (!templateQuery || !csrf) { - return false; - } - let res = await getFeedbackTemplate(templateQuery, csrf); - let templateResponse = - res.payload && - res.payload.data && - res.payload.status === 200 && - !res.error - ? res.payload.data - : null - if (templateResponse === null) { - window.snackDispatch({ - type: UPDATE_TOAST, - payload: { - severity: "error", - toast: "The ID for the template you selected does not exist.", - }, - }); - return false; - } - else { - return true; - } - } - - isTemplateValid().then((isValid) => { - setTemplateIsValid(isValid); - }); - }, [csrf, templateQuery]); + if (!memberIds.length) return true; + return !!query.for && memberIds.includes(query.for); + }, [query.for, memberIds]); const hasFrom = useCallback(() => { + if (!memberIds.length) return true; let from = query.from; if (from) { from = Array.isArray(from) ? from : [from]; @@ -182,38 +140,64 @@ const FeedbackRequestPage = () => { }, []); const hasSend = useCallback(() => { - const isValidPair = dueQuery ? dueQuery >= sendQuery : true; - return (sendQuery && isValidDate(sendQuery) && isValidPair) - }, [sendQuery, isValidDate, dueQuery]); + const isValidPair = query.due ? query.due >= query.send : true; + return (query.send && isValidDate(query.send) && isValidPair) + }, [query.send, isValidDate, query.due]); const canProceed = useCallback(() => { if (query && Object.keys(query).length > 0) { - switch (activeStep) { - case 1: - return hasFor() && templateIsValid - case 2: - return hasFor() && templateIsValid && hasFrom(); - case 3: - const dueQueryValid = dueQuery ? isValidDate(dueQuery) : true; - return hasFor() && templateIsValid && hasFrom() && hasSend() && dueQueryValid; - default: - return false; + if (activeStep === 1) { + return hasFor() && templateIsValid; + } else if (activeStep === 2) { + return hasFor() && templateIsValid && hasFrom(); + } else if (activeStep === 3) { + const dueQueryValid = query.due ? isValidDate(query.due) : true; + return hasFor() && templateIsValid && hasFrom() && hasSend() && dueQueryValid; + } else { + return false; } } return false; - }, [activeStep, hasFor, hasFrom, hasSend, dueQuery, isValidDate, query, templateIsValid]); + }, [hasFor, hasFrom, hasSend, isValidDate, query, templateIsValid, activeStep]); + + const sendFeedbackRequest = async (feedbackRequest) => { + if (csrf) { + let res = await createFeedbackRequest(feedbackRequest, csrf); + let data = + res.payload && res.payload.data && !res.error + ? res.payload.data + : null; + if (data) { + // If the request was successful, set created ad-hoc templates to inactive + await softDeleteAdHoc(currentUserId); + const newLocation = { + pathname: "/feedback/request/confirmation", + search: queryString.stringify(query), + } + history.push(newLocation) + } else if (res.error || data === null) { + dispatch({ + type: UPDATE_TOAST, + payload: { + severity: "error", + toast: "An error has occurred while submitting your request.", + }, + }); + } + } + } const handleSubmit = () => { - const from = fromQuery ? (Array.isArray(fromQuery) ? fromQuery : [fromQuery]) : []; + const from = query.from ? (Array.isArray(query.from) ? query.from : [query.from]) : []; for (const recipient of from) { const feedbackRequest = { id: null, creatorId: currentUserId, - requesteeId: forQuery, + requesteeId: query.for, recipientId: recipient, - templateId: templateQuery, - sendDate: sendDate, - dueDate: dueQuery, + templateId: query.template, + sendDate: query.send, + dueDate: query.due, status: "pending", submitDate: null }; @@ -230,13 +214,13 @@ const FeedbackRequestPage = () => { query.step = `${activeStep + 1}`; history.push({...location, search: queryString.stringify(query)}); - }, [canProceed, activeStep, steps.length, query, location, history]); // eslint-disable-line react-hooks/exhaustive-deps + }, [canProceed, steps.length, query, location, history]); // eslint-disable-line react-hooks/exhaustive-deps const onBackClick = useCallback(() => { if (activeStep === 1) return; query.step = `${activeStep - 1}`; history.push({ ...location, search: queryString.stringify(query) }); - }, [activeStep, query, location, history]); + }, [query, location, history, activeStep]); const softDeleteAdHoc = useCallback(async (creatorId) => { if (csrf) { @@ -244,52 +228,80 @@ const FeedbackRequestPage = () => { } }, [csrf]); - const sendFeedbackRequest = async (feedbackRequest) => { - if (csrf) { - let res = await createFeedbackRequest(feedbackRequest, csrf); - let data = - res.payload && res.payload.data && !res.error + const urlIsValid = useCallback(() => { + if (query) { + if (activeStep === 1) { + return hasFor(); + } else if (activeStep === 2) { + return hasFor() && templateIsValid; + } else if (activeStep === 3) { + return hasFor() && templateIsValid && hasFrom(); + } else { + return false; + } + } + return false; + }, [hasFor, hasFrom, query, templateIsValid, activeStep]); + + useEffect(() => { + setActiveStep(getStep()); + }, [query.step, getStep]); + + useEffect(() => { + const members = selectCurrentMemberIds(state); + if (members) { + setMemberIds(members); + } + }, [state]); + + useEffect(() => { + const params = queryString.parse(location?.search); + setQuery(params); + }, [location.search]); + + useEffect(() => { + async function isTemplateValid() { + if (!query.template || !csrf) { + return false; + } + let res = await getFeedbackTemplate(query.template, csrf); + let templateResponse = + res.payload && + res.payload.data && + res.payload.status === 200 && + !res.error ? res.payload.data - : null; - if (data) { - // If the request was successful, set created ad-hoc templates to inactive - await softDeleteAdHoc(currentUserId); - const newLocation = { - pathname: "/feedback/request/confirmation", - search: queryString.stringify(query), - } - history.push(newLocation) - } else if(res.error || data === null) { - dispatch({ + : null + if (templateResponse === null) { + window.snackDispatch({ type: UPDATE_TOAST, payload: { severity: "error", - toast: "An error has occurred while submitting your request.", + toast: "The ID for the template you selected does not exist.", }, }); + return false; + } + else { + return true; } } - } - const urlIsValid = useCallback(() => { - if (query && Object.keys(query).length > 0) { - switch (activeStep) { - case 1: - return hasFor(); - case 2: - return hasFor() && templateIsValid - case 3: - return hasFor() && templateIsValid && hasFrom(); - case 4: - return hasFor() && templateIsValid && hasFrom() && hasSend(); - default: - return false; - } + if (queryLoaded.current && csrf) { + isTemplateValid().then((isValid) => { + setTemplateIsValid(isValid); + }); + } else { + queryLoaded.current = true; } - return true; - }, [activeStep, hasFor, hasFrom, hasSend, query, templateIsValid]); + }, [csrf, query, queryLoaded]); useEffect(() => { + if (!queryLoaded.current || templateIsValid === undefined) return; + + if (query.for) { + setRequestee(selectProfile(state, query.for)); + } if (!urlIsValid()) { dispatch({ type: UPDATE_TOAST, @@ -302,11 +314,11 @@ const FeedbackRequestPage = () => { history.push("/checkins"); }); } - }, [history, urlIsValid, dispatch, softDeleteAdHoc, currentUserId]); + }, [history, state, query, currentUserId, dispatch, softDeleteAdHoc, urlIsValid, templateIsValid]); - useEffect(()=> { + useEffect(() => { setReadyToProceed(canProceed()); - }, [canProceed]) + }, [canProceed]); return (