diff --git a/client/src/App.js b/client/src/App.js index 20ed8591..4c106040 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -40,6 +40,7 @@ import Logout from "./components/Authorization/Logout"; import { getConfigs } from "./helpers/Config"; const calculationPath = "/calculation/:page/:projectId?/*"; +const sharedProjectPath = "/projects/:projectId?"; const App = () => { const contentContainerRef = useRef(); @@ -68,6 +69,14 @@ const App = () => { } /> + + } + /> 0 && account?.id) { - projectResponse = await projectService.getById(projectId); - - // setLoginId(projectResponse.data.loginId); - // setDateModified(formatDatetime(projectResponse.data.dateModified)); - // setDateSnapshotted( - // formatDatetime(projectResponse.data?.dateSnapshotted) - // ); - // setDateSubmitted(formatDatetime(projectResponse.data?.dateSubmitted)); - setProject(projectResponse.data); + if (location.pathname.split("/")[1] == "projects") { + projectResponse = await projectService.getByIdWithEmail(projectId); - inputs = JSON.parse(projectResponse.data.formInputs); - setStrategiesInitialized(true); + if (projectResponse) { + setShareView(true); + } else { + navigate("/unauthorized"); + } + } else { + projectResponse = await projectService.getById(projectId); + + // setLoginId(projectResponse.data.loginId); + // setDateModified(formatDatetime(projectResponse.data.dateModified)); + // setDateSnapshotted( + // formatDatetime(projectResponse.data?.dateSnapshotted) + // ); + // setDateSubmitted(formatDatetime(projectResponse.data?.dateSubmitted)); + setShareView(false); + } + if (projectResponse) { + setProject(projectResponse); + inputs = JSON.parse(projectResponse.data.formInputs); + setStrategiesInitialized(true); + } } else { + setShareView(false); setStrategiesInitialized(false); } engine.run(inputs, resultRuleCodes); @@ -104,7 +118,7 @@ export function TdmCalculationContainer({ contentContainerRef }) { // const redirect = account.id ? "/projects" : "/login"; // navigate(redirect); } - }, [engine, projectId, account, setRules, setProject]); + }, [engine, projectId, account, location, navigate, setRules, setProject]); // Initialize the engine with saved project data, as appropriate. // Should run only when projectId changes. @@ -543,6 +557,7 @@ export function TdmCalculationContainer({ contentContainerRef }) { inapplicableStrategiesModal={inapplicableStrategiesModal} closeStrategiesModal={closeStrategiesModal} project={project} + shareView={shareView} /> ); } diff --git a/client/src/components/ProjectWizard/TdmCalculationWizard.jsx b/client/src/components/ProjectWizard/TdmCalculationWizard.jsx index 8922b0a2..36943218 100644 --- a/client/src/components/ProjectWizard/TdmCalculationWizard.jsx +++ b/client/src/components/ProjectWizard/TdmCalculationWizard.jsx @@ -55,7 +55,8 @@ const TdmCalculationWizard = props => { contentContainerRef, inapplicableStrategiesModal, closeStrategiesModal, - project + project, + shareView } = props; const classes = useStyles(); const context = useContext(ToastContext); @@ -63,7 +64,7 @@ const TdmCalculationWizard = props => { const account = userContext ? userContext.account : null; const params = useParams(); const navigate = useNavigate(); - const page = Number(params.page || 1); + const page = Number(shareView ? 5 : params.page || 1); const projectId = Number(params.projectId); const { pathname } = useLocation(); const [ainInputError, setAINInputError] = useState(""); @@ -104,10 +105,13 @@ const TdmCalculationWizard = props => { !projectId) ); }; - - return formIsDirty && !isSameProject(currentLocation, nextLocation); + return ( + !shareView && + formIsDirty && + !isSameProject(currentLocation, nextLocation) + ); }, - [formIsDirty, projectId] + [formIsDirty, projectId, shareView] ); const blocker = useBlocker(shouldBlock); @@ -190,7 +194,7 @@ const TdmCalculationWizard = props => { const setDisplaySaveButton = () => { const loggedIn = !!account && !!account.id; const setDisplayed = loggedIn; - return setDisplayed; + return !shareView && setDisplayed; }; const setDisplayPrintButton = () => { @@ -201,7 +205,7 @@ const TdmCalculationWizard = props => { }; const setDisplaySubmitButton = () => { - if (page === 5) { + if (page === 5 && !shareView) { return true; } return false; @@ -298,8 +302,8 @@ const TdmCalculationWizard = props => { rules={rules} account={account} projectId={projectId} - loginId={loginId} - onSave={onSave} + loginId={!shareView ? loginId : account?.id} + onSave={!shareView ? onSave : null} dateModified={project.dateModified} /> ); @@ -338,6 +342,7 @@ const TdmCalculationWizard = props => { setDisplaySubmitButton={setDisplaySubmitButton} onSave={onSave} project={project} + shareView={shareView} /> @@ -387,7 +392,8 @@ TdmCalculationWizard.propTypes = { // dateSubmitted: PropTypes.string, inapplicableStrategiesModal: PropTypes.bool, closeStrategiesModal: PropTypes.func, - project: PropTypes.any + project: PropTypes.any, + shareView: PropTypes.bool }; export default TdmCalculationWizard; diff --git a/client/src/components/ProjectWizard/WizardFooter.jsx b/client/src/components/ProjectWizard/WizardFooter.jsx index 2137673d..ede291bc 100644 --- a/client/src/components/ProjectWizard/WizardFooter.jsx +++ b/client/src/components/ProjectWizard/WizardFooter.jsx @@ -51,7 +51,8 @@ const WizardFooter = ({ setDisplayPrintButton, setDisplaySubmitButton, onSave, - project + project, + shareView }) => { const classes = useStyles(); const componentRef = useRef(); @@ -76,7 +77,7 @@ const WizardFooter = ({ navDirection="previous" color="colorPrimary" isVisible={page !== 1} - isDisabled={Number(page) === 1} + isDisabled={shareView || Number(page) === 1} onClick={() => { onPageChange(Number(page) - 1); }} @@ -174,7 +175,8 @@ WizardFooter.propTypes = { setDisplaySubmitButton: PropTypes.any, onSave: PropTypes.any, onDownload: PropTypes.any, - project: PropTypes.any + project: PropTypes.any, + shareView: PropTypes.bool }; export default WizardFooter; diff --git a/client/src/services/project.service.js b/client/src/services/project.service.js index 49f9fe74..5b196323 100644 --- a/client/src/services/project.service.js +++ b/client/src/services/project.service.js @@ -14,6 +14,10 @@ export function getById(id) { return axios.get(`${baseUrl}/${id}`); } +export function getByIdWithEmail(id) { + return axios.get(`${baseUrl}/projectShare/${id}`); +} + export function post(project) { return axios.post(baseUrl, project); } diff --git a/server/app/controllers/project.controller.js b/server/app/controllers/project.controller.js index 5e84751e..e1093950 100644 --- a/server/app/controllers/project.controller.js +++ b/server/app/controllers/project.controller.js @@ -24,6 +24,30 @@ const getById = async (req, res) => { } }; +const getByIdWithEmail = async (req, res) => { + try { + const project = await projectService.getByIdWithEmail( + req.params.id, + req.user.email + ); + if (!project) { + res + .status(404) + .send( + "project " + + req.params.id + + " not shared with " + + req.user.email + + " or does not exist." + ); + return; + } + res.status(200).json(project); + } catch (err) { + res.status(500).send(err); + } +}; + const post = async (req, res) => { try { if (req.body.loginId !== req.user.id) { @@ -229,6 +253,7 @@ const updateTotals = async (req, res) => { module.exports = { getAll, getById, + getByIdWithEmail, post: [validate({ body: projectSchema }), post, validationErrorMiddleware], put, del, diff --git a/server/app/routes/project.routes.js b/server/app/routes/project.routes.js index 8847543c..ab7d335c 100644 --- a/server/app/routes/project.routes.js +++ b/server/app/routes/project.routes.js @@ -11,6 +11,11 @@ router.get( ); router.get("/", jwtSession.validateUser, projectController.getAll); router.get("/:id", jwtSession.validateUser, projectController.getById); +router.get( + "/projectShare/:id/", + jwtSession.validateUser, + projectController.getByIdWithEmail +); router.post("/", jwtSession.validateUser, projectController.post); router.put("/hide", jwtSession.validateUser, projectController.hide); router.put("/trash", jwtSession.validateUser, projectController.trash); diff --git a/server/app/services/project.service.js b/server/app/services/project.service.js index 063d1486..622cfdd2 100644 --- a/server/app/services/project.service.js +++ b/server/app/services/project.service.js @@ -30,6 +30,23 @@ const getById = async (loginId, id) => { } }; +const getByIdWithEmail = async (id, email) => { + try { + await poolConnect; + const request = pool.request(); + request.input("email", mssql.NVarChar, email); + request.input("Id", mssql.Int, id); + const response = await request.execute("Project_SelectByIdWithSharedEmail"); + if (response.recordset && response.recordset.length > 0) { + return response.recordset[0]; + } else { + return null; + } + } catch (err) { + return Promise.reject(err); + } +}; + const post = async item => { try { await poolConnect; @@ -264,6 +281,7 @@ const updateTotals = async ( module.exports = { getAll, getById, + getByIdWithEmail, post, put, del, diff --git a/server/db/migration/V20241112.1450__add_project_select_by_id_with_shared_email.sql b/server/db/migration/V20241112.1450__add_project_select_by_id_with_shared_email.sql new file mode 100644 index 00000000..f06bb58a --- /dev/null +++ b/server/db/migration/V20241112.1450__add_project_select_by_id_with_shared_email.sql @@ -0,0 +1,27 @@ +CREATE proc [dbo].[Project_SelectByIdWithSharedEmail] + @email NVARCHAR(100), + @id INT +AS +BEGIN + SELECT + p.id, + p.name, + p.address, + p.formInputs, + p.loginId, + p.calculationId, + p.dateCreated, + p.dateModified, + p.description, + p.droId, + p.adminNotes, + p.dateModifiedAdmin, + p.dateHidden, + p.dateTrashed, + p.dateSnapshotted, + p.dateSubmitted + FROM Project p + RIGHT JOIN ProjectShare ps ON ps.projectId = p.id + WHERE ps.email = @email AND ps.projectid = @id; +END +GO \ No newline at end of file