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