diff --git a/migrations/sql/V2024.06.06.15.12__add_vc_connection_deleted_ind.sql b/migrations/sql/V2024.06.06.15.12__add_vc_connection_deleted_ind.sql
new file mode 100644
index 0000000000..43a5fb7211
--- /dev/null
+++ b/migrations/sql/V2024.06.06.15.12__add_vc_connection_deleted_ind.sql
@@ -0,0 +1 @@
+ALTER TABLE party_verifiable_credential_connection ADD deleted_ind boolean default false;
diff --git a/services/common/src/components/forms/FormWrapper.tsx b/services/common/src/components/forms/FormWrapper.tsx
index 707f047d55..14eb1f03ba 100644
--- a/services/common/src/components/forms/FormWrapper.tsx
+++ b/services/common/src/components/forms/FormWrapper.tsx
@@ -56,7 +56,7 @@ NOTABLE OMISSIONS:
SEE ALSO:
- BaseInput.tsx
*/
-interface FormWrapperProps {
+export interface FormWrapperProps {
name: string;
initialValues?: any;
reduxFormConfig?: Partial;
diff --git a/services/common/src/components/forms/RenderFileUpload.tsx b/services/common/src/components/forms/RenderFileUpload.tsx
index 915f48a229..430dcf3eee 100644
--- a/services/common/src/components/forms/RenderFileUpload.tsx
+++ b/services/common/src/components/forms/RenderFileUpload.tsx
@@ -134,8 +134,11 @@ export const FileUpload = (props: FileUploadProps) => {
const fileTypeList = listedFileTypes ?? Object.keys(acceptedFileTypesMap);
const fileTypeDisplayString =
fileTypeList.slice(0, -1).join(", ") + ", and " + fileTypeList.slice(-1);
+ const fileSize = props.maxFileSize
+ ? ` with max individual file size of ${props.maxFileSize}`
+ : "";
const secondLine = abbrevLabel
- ? `We accept most common ${fileTypeDisplayString} files
`
+ ? `We accept most common ${fileTypeDisplayString} files${fileSize}.
`
: `Accepted filetypes: ${fileTypeDisplayString}
`;
return `${labelInstruction}
${secondLine}`;
};
diff --git a/services/common/src/components/forms/RenderSelect.tsx b/services/common/src/components/forms/RenderSelect.tsx
index 3f503f8786..71b01a4c9c 100644
--- a/services/common/src/components/forms/RenderSelect.tsx
+++ b/services/common/src/components/forms/RenderSelect.tsx
@@ -61,6 +61,7 @@ export const RenderSelect: FC = ({
>
}
+ type="warning"
+ showIcon
+ />
+ )}
+
+ >
+ );
+};
diff --git a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectContacts.tsx b/services/common/src/components/projectSummary/ProjectContacts.tsx
similarity index 94%
rename from services/minespace-web/src/components/Forms/projects/projectSummary/ProjectContacts.tsx
rename to services/common/src/components/projectSummary/ProjectContacts.tsx
index 59f8b001c3..2e7b3431d7 100644
--- a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectContacts.tsx
+++ b/services/common/src/components/projectSummary/ProjectContacts.tsx
@@ -12,17 +12,15 @@ import {
email,
postalCodeWithCountry,
} from "@mds/common/redux/utils/Validate";
-import { normalizePhone } from "@common/utils/helpers";
-import LinkButton from "@/components/common/LinkButton";
-import * as FORM from "@/constants/forms";
+import { normalizePhone } from "@mds/common/redux/utils/helpers";
+import LinkButton from "@mds/common/components/common/LinkButton";
+import { FORM } from "@mds/common/constants/forms";
import RenderField from "@mds/common/components/forms/RenderField";
import RenderSelect from "@mds/common/components/forms/RenderSelect";
-import { CONTACTS_COUNTRY_OPTIONS } from "@mds/common";
-
+import { CONTACTS_COUNTRY_OPTIONS } from "@mds/common/constants";
import { getDropdownProvinceOptions } from "@mds/common/redux/selectors/staticContentSelectors";
-const RenderContacts = (props) => {
- const { fields, contacts } = props;
+const RenderContacts = ({ fields }) => {
const dispatch = useDispatch();
const provinceOptions = useSelector(getDropdownProvinceOptions);
const handleClearProvince = (currentCountry, addressTypeCode, subDivisionCode, field) => {
@@ -38,14 +36,14 @@ const RenderContacts = (props) => {
}
}
};
-
return (
<>
{fields.map((field, index) => {
- const { address = {} } = contacts[index] ?? {};
+ const contact = fields.get(index);
+ const { address = {} } = contact ?? {};
const { address_type_code, sub_division_code } = address ?? {};
const isInternational = address_type_code === "INT";
- const isPrimary = contacts[index].is_primary;
+ const isPrimary = contact.is_primary;
return (
// eslint-disable-next-line react/no-array-index-key
@@ -255,7 +253,7 @@ export const ProjectContacts: FC = () => {
return (
<>
Project Contacts
-
+
>
);
};
diff --git a/services/common/src/components/projectSummary/ProjectDates.spec.tsx b/services/common/src/components/projectSummary/ProjectDates.spec.tsx
new file mode 100644
index 0000000000..b786aeda07
--- /dev/null
+++ b/services/common/src/components/projectSummary/ProjectDates.spec.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+import { render } from "@testing-library/react";
+import FormWrapper from "@mds/common/components/forms/FormWrapper";
+import { FORM } from "@mds/common/constants/forms";
+import { ReduxWrapper } from "@mds/common/tests/utils/ReduxWrapper";
+import * as MOCK from "@mds/common/tests/mocks/dataMocks";
+import { PERMITS, PROJECTS, STATIC_CONTENT } from "@mds/common/constants/reducerTypes";
+import ProjectDates from "./ProjectDates";
+
+const initialState = {
+ [PROJECTS]: {
+ projectSummary: MOCK.PROJECT_SUMMARY,
+ },
+ [PERMITS]: {
+ permits: MOCK.PERMITS,
+ },
+ [STATIC_CONTENT]: {
+ projectSummaryPermitTypes: MOCK.BULK_STATIC_CONTENT_RESPONSE.projectSummaryPermitTypes,
+ projectSummaryAuthorizationTypes:
+ MOCK.BULK_STATIC_CONTENT_RESPONSE.projectSummaryAuthorizationTypes,
+ },
+};
+
+describe("ProjectDates", () => {
+ it("renders properly", () => {
+ const { container } = render(
+
+ {}}
+ >
+
+
+
+ );
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectDates.js b/services/common/src/components/projectSummary/ProjectDates.tsx
similarity index 53%
rename from services/minespace-web/src/components/Forms/projects/projectSummary/ProjectDates.js
rename to services/common/src/components/projectSummary/ProjectDates.tsx
index 3db39ed24c..55326e89e6 100644
--- a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectDates.js
+++ b/services/common/src/components/projectSummary/ProjectDates.tsx
@@ -1,26 +1,19 @@
import React from "react";
-import PropTypes from "prop-types";
-import { Field, formValueSelector } from "redux-form";
+import { useSelector } from "react-redux";
+import { Field, getFormValues } from "redux-form";
import { Typography } from "antd";
-import { connect } from "react-redux";
-import { dateNotBeforeOther, dateNotAfterOther } from "@common/utils/Validate";
-import Callout from "@/components/common/Callout";
-import * as FORM from "@/constants/forms";
+import { dateNotBeforeOther, dateNotAfterOther } from "@mds/common/redux/utils/Validate";
+import Callout from "@mds/common/components/common/Callout";
+import { FORM } from "@mds/common/constants/forms";
import RenderDate from "@mds/common/components/forms/RenderDate";
-const propTypes = {
- expected_permit_application_date: PropTypes.string,
- expected_draft_irt_submission_date: PropTypes.string,
- expected_permit_receipt_date: PropTypes.string,
-};
-
-const defaultProps = {
- expected_permit_application_date: undefined,
- expected_draft_irt_submission_date: undefined,
- expected_permit_receipt_date: undefined,
-};
+export const ProjectDates = () => {
+ const {
+ expected_permit_application_date,
+ expected_draft_irt_submission_date,
+ expected_permit_receipt_date,
+ } = useSelector(getFormValues(FORM.ADD_EDIT_PROJECT_SUMMARY));
-export const ProjectDates = (props) => {
return (
<>
Project Dates
@@ -32,7 +25,7 @@ export const ProjectDates = (props) => {
Major Mines Office
@@ -47,7 +40,7 @@ export const ProjectDates = (props) => {
label="When do you anticipate submitting a draft Information Requirements Table?"
placeholder="Please select date"
component={RenderDate}
- validate={[dateNotAfterOther(props.expected_permit_application_date)]}
+ validate={[dateNotAfterOther(expected_permit_application_date)]}
/>
{
label="When do you anticipate submitting a permit application?"
placeholder="Please select date"
component={RenderDate}
- validate={[dateNotBeforeOther(props.expected_draft_irt_submission_date)]}
+ validate={[dateNotBeforeOther(expected_draft_irt_submission_date)]}
/>
{
label="When do you hope to receive your permit/amendment(s)?"
placeholder="Please select date"
component={RenderDate}
- validate={[dateNotBeforeOther(props.expected_permit_application_date)]}
+ validate={[dateNotBeforeOther(expected_permit_application_date)]}
/>
{
label="When do you anticipate starting work on this project?"
placeholder="Please select date"
component={RenderDate}
- validate={[dateNotBeforeOther(props.expected_permit_receipt_date)]}
+ validate={[dateNotBeforeOther(expected_permit_receipt_date)]}
/>
>
);
};
-ProjectDates.propTypes = propTypes;
-ProjectDates.defaultProps = defaultProps;
-
-const selector = formValueSelector(FORM.ADD_EDIT_PROJECT_SUMMARY);
-const mapStateToProps = (state) => ({
- expected_draft_irt_submission_date: selector(state, "expected_draft_irt_submission_date"),
- expected_permit_application_date: selector(state, "expected_permit_application_date"),
- expected_permit_receipt_date: selector(state, "expected_permit_receipt_date"),
-});
-
-export default connect(mapStateToProps)(ProjectDates);
+export default ProjectDates;
diff --git a/services/common/src/components/projectSummary/ProjectLinks.spec.tsx b/services/common/src/components/projectSummary/ProjectLinks.spec.tsx
new file mode 100644
index 0000000000..abc5d69e6c
--- /dev/null
+++ b/services/common/src/components/projectSummary/ProjectLinks.spec.tsx
@@ -0,0 +1,41 @@
+import React from "react";
+import { render } from "@testing-library/react";
+import ProjectLinks from "./ProjectLinks";
+import { ReduxWrapper } from "@mds/common/tests/utils/ReduxWrapper";
+import { PROJECTS } from "@mds/common/constants/reducerTypes";
+import FormWrapper from "../forms/FormWrapper";
+import { FORM } from "../..";
+import * as MOCK from "@mds/common/tests/mocks/dataMocks";
+
+const initialState = {
+ [PROJECTS]: {
+ project: MOCK.PROJECT,
+ projectSummary: MOCK.PROJECT_SUMMARY,
+ projects: MOCK.PROJECTS.records,
+ },
+};
+
+describe("ProjectLinks Component", () => {
+ test("renders ProjectLinks with correct props", () => {
+ const { getByText } = render(
+
+ {}}
+ >
+ ""} />
+
+
+ );
+ expect(getByText("Related Project Applications")).toBeDefined();
+ expect(
+ getByText("Description of related major project applications for this mine are listed below.")
+ ).toBeDefined();
+ expect(getByText("Project Title")).toBeDefined();
+ // test is otherwise working but table is not populating
+ // expect(getByText(MOCK.PROJECT.project_links[0].related_project.project_title)).toBeTruthy();
+ // // Check if status is inactive when either project summary, major mine application, or irt status is WDN
+ // expect(getByText("Inactive")).toBeDefined();
+ });
+});
diff --git a/services/common/src/components/projects/ProjectLinks.tsx b/services/common/src/components/projectSummary/ProjectLinks.tsx
similarity index 98%
rename from services/common/src/components/projects/ProjectLinks.tsx
rename to services/common/src/components/projectSummary/ProjectLinks.tsx
index af4eb8f3f0..6108ed9b86 100644
--- a/services/common/src/components/projects/ProjectLinks.tsx
+++ b/services/common/src/components/projectSummary/ProjectLinks.tsx
@@ -2,7 +2,7 @@ import React, { FC, useEffect, useState } from "react";
import { getProject, getProjects } from "@mds/common/redux/selectors/projectSelectors";
import { useSelector, useDispatch } from "react-redux";
import { Field, change } from "redux-form";
-import ProjectLinksTable from "@mds/common/components/projects/ProjectLinksTable";
+import ProjectLinksTable from "@mds/common/components/projectSummary/ProjectLinksTable";
import { ILinkedProject, IProject } from "@mds/common/interfaces";
import { Button, Col, Row, Typography } from "antd";
diff --git a/services/common/src/components/projects/ProjectLinksTable.tsx b/services/common/src/components/projectSummary/ProjectLinksTable.tsx
similarity index 100%
rename from services/common/src/components/projects/ProjectLinksTable.tsx
rename to services/common/src/components/projectSummary/ProjectLinksTable.tsx
diff --git a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryFileUpload.tsx b/services/common/src/components/projectSummary/ProjectSummaryFileUpload.tsx
similarity index 99%
rename from services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryFileUpload.tsx
rename to services/common/src/components/projectSummary/ProjectSummaryFileUpload.tsx
index f1de7a3be0..87e4ecfcaa 100644
--- a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryFileUpload.tsx
+++ b/services/common/src/components/projectSummary/ProjectSummaryFileUpload.tsx
@@ -6,7 +6,7 @@ import RenderFileUpload from "@mds/common/components/forms/RenderFileUpload";
import { Alert, Divider, Modal, Popconfirm, Table, Typography } from "antd";
import { getUserInfo } from "@mds/common/redux/selectors/authenticationSelectors";
import { FilePondFile } from "filepond";
-import { IDocument } from "@mds/common";
+import { IDocument } from "@mds/common/interfaces/document";
const notificationDisabledStatusCodes = [409]; // Define the notification disabled status codes
diff --git a/services/common/src/components/projectSummary/ProjectSummaryForm.tsx b/services/common/src/components/projectSummary/ProjectSummaryForm.tsx
new file mode 100644
index 0000000000..87bf4a7710
--- /dev/null
+++ b/services/common/src/components/projectSummary/ProjectSummaryForm.tsx
@@ -0,0 +1,182 @@
+import React, { FC } from "react";
+import { useSelector } from "react-redux";
+import { FORM } from "@mds/common/constants/forms";
+import DocumentUpload from "@mds/common/components/projectSummary/DocumentUpload";
+import ProjectContacts from "@mds/common/components/projectSummary/ProjectContacts";
+import ProjectDates from "@mds/common/components/projectSummary/ProjectDates";
+import AuthorizationsInvolved from "@mds/common/components/projectSummary/AuthorizationsInvolved";
+import SteppedForm from "@mds/common/components/forms/SteppedForm";
+import Step from "@mds/common/components/forms/Step";
+import ProjectLinks from "@mds/common/components/projectSummary/ProjectLinks";
+import { useFeatureFlag } from "@mds/common/providers/featureFlags/useFeatureFlag";
+import { IProjectSummary } from "@mds/common/interfaces/projects";
+import { Feature } from "@mds/common/utils/featureFlag";
+import { Agent } from "./Agent";
+import { LegalLandOwnerInformation } from "@mds/common/components/projectSummary/LegalLandOwnerInformation";
+import { FacilityOperator } from "@mds/common/components/projectSummary/FacilityOperator";
+import BasicInformation from "@mds/common/components/projectSummary/BasicInformation";
+import Applicant from "@mds/common/components/projectSummary/Applicant";
+import Declaration from "@mds/common/components/projectSummary/Declaration";
+import { ApplicationSummary } from "./ApplicationSummary";
+import { removeNullValuesRecursive } from "@mds/common/constants/utils";
+import { getProjectSummaryAuthorizationTypesArray } from "@mds/common/redux/selectors/staticContentSelectors";
+import { isArray } from "lodash";
+import { MinistryContact } from "./MinistryContact";
+import { getSystemFlag } from "@mds/common/redux/selectors/authenticationSelectors";
+import { SystemFlagEnum } from "../..";
+
+interface ProjectSummaryFormProps {
+ initialValues: IProjectSummary;
+ handleTabChange: (newTab: string) => void;
+ handleSaveData: (formValues, newActiveTab?) => Promise;
+ activeTab: string;
+ isEditMode?: boolean;
+}
+
+// converted to a function to make feature flag easier to work with
+// when removing feature flag, convert back to array
+export const getProjectFormTabs = (amsFeatureEnabled: boolean, isCore = false) => {
+ const projectFormTabs = [
+ "basic-information",
+ "related-projects",
+ "purpose-and-authorization",
+ "project-contacts",
+ "applicant-information",
+ "representing-agent",
+ "location-access-and-land-use",
+ "mine-components-and-offsite-infrastructure",
+ "project-dates",
+ "document-upload",
+ "application-summary",
+ "declaration",
+ ];
+ if (isCore) {
+ projectFormTabs.splice(1, 0, "ministry-contact");
+ }
+
+ return amsFeatureEnabled
+ ? projectFormTabs
+ : [
+ "basic-information",
+ "related-projects",
+ "project-contacts",
+ "project-dates",
+ "document-upload",
+ ];
+};
+
+export const ProjectSummaryForm: FC = ({
+ initialValues,
+ handleSaveData,
+ handleTabChange,
+ activeTab,
+ isEditMode = true,
+}) => {
+ const systemFlag = useSelector(getSystemFlag);
+ const isCore = systemFlag === SystemFlagEnum.core;
+ const { isFeatureEnabled } = useFeatureFlag();
+ const majorProjectsFeatureEnabled = isFeatureEnabled(Feature.MAJOR_PROJECT_LINK_PROJECTS);
+ const amsFeatureEnabled = isFeatureEnabled(Feature.AMS_AGENT);
+ const projectFormTabs = getProjectFormTabs(amsFeatureEnabled, isCore);
+ const projectSummaryAuthorizationTypesArray = useSelector(
+ getProjectSummaryAuthorizationTypesArray
+ );
+
+ const transformAuthorizations = (valuesFromForm: any) => {
+ const { authorizations = {}, project_summary_guid } = valuesFromForm;
+
+ const transformAuthorization = (type, authorization) => {
+ return { ...authorization, project_summary_authorization_type: type, project_summary_guid };
+ };
+
+ let updatedAuthorizations = [];
+ let newAmsAuthorizations = [];
+ let amendAmsAuthorizations = [];
+
+ projectSummaryAuthorizationTypesArray.forEach((type) => {
+ const authsOfType = authorizations[type];
+ if (authsOfType) {
+ if (isArray(authsOfType)) {
+ const formattedAuthorizations = authsOfType.map((a) => {
+ return transformAuthorization(type, a);
+ });
+ updatedAuthorizations = updatedAuthorizations.concat(formattedAuthorizations);
+ } else {
+ newAmsAuthorizations = newAmsAuthorizations.concat(
+ authsOfType?.NEW.map((a) =>
+ transformAuthorization(type, {
+ ...a,
+ project_summary_permit_type: ["NEW"],
+ })
+ )
+ );
+ amendAmsAuthorizations = amendAmsAuthorizations.concat(
+ authsOfType?.AMENDMENT.map((a) =>
+ transformAuthorization(type, {
+ ...a,
+ project_summary_permit_type: ["AMENDMENT"],
+ })
+ )
+ );
+ }
+ }
+ });
+ return {
+ authorizations: updatedAuthorizations,
+ ams_authorizations: { amendments: amendAmsAuthorizations, new: newAmsAuthorizations },
+ };
+ };
+
+ const handleTransformPayload = (valuesFromForm: any) => {
+ let payloadValues: any = {};
+ const updatedAuthorizations = transformAuthorizations(valuesFromForm);
+ const values = removeNullValuesRecursive(valuesFromForm);
+ payloadValues = {
+ ...values,
+ ...updatedAuthorizations,
+ };
+ delete payloadValues.authorizationTypes;
+ return payloadValues;
+ };
+
+ const renderTabComponent = (tab) =>
+ ({
+ "ministry-contact": ,
+ "location-access-and-land-use": ,
+ "basic-information": ,
+ "related-projects": (
+ GLOBAL_ROUTES?.EDIT_PROJECT.dynamicRoute(p.project_guid)}
+ />
+ ),
+ "project-contacts": ,
+ "project-dates": ,
+ "applicant-information": ,
+ "representing-agent": ,
+ "mine-components-and-offsite-infrastructure": ,
+ "purpose-and-authorization": ,
+ "document-upload": ,
+ "application-summary": ,
+ declaration: ,
+ }[tab]);
+
+ return (
+
+ {projectFormTabs
+ .filter((tab) => majorProjectsFeatureEnabled || tab !== "related-projects")
+ .map((tab) => (
+ {renderTabComponent(tab)}
+ ))}
+
+ );
+};
+
+export default ProjectSummaryForm;
diff --git a/services/common/src/components/projectSummary/__snapshots__/DocumentUpload.spec.tsx.snap b/services/common/src/components/projectSummary/__snapshots__/DocumentUpload.spec.tsx.snap
new file mode 100644
index 0000000000..ed9943576d
--- /dev/null
+++ b/services/common/src/components/projectSummary/__snapshots__/DocumentUpload.spec.tsx.snap
@@ -0,0 +1,491 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DocumentUpload renders properly 1`] = `
+
+`;
diff --git a/services/common/src/components/projectSummary/__snapshots__/ProjectDates.spec.tsx.snap b/services/common/src/components/projectSummary/__snapshots__/ProjectDates.spec.tsx.snap
new file mode 100644
index 0000000000..f6e2e8ce34
--- /dev/null
+++ b/services/common/src/components/projectSummary/__snapshots__/ProjectDates.spec.tsx.snap
@@ -0,0 +1,404 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ProjectDates renders properly 1`] = `
+
+`;
diff --git a/services/common/src/constants/reducerTypes.ts b/services/common/src/constants/reducerTypes.ts
index 8461448ee3..3338b2e453 100644
--- a/services/common/src/constants/reducerTypes.ts
+++ b/services/common/src/constants/reducerTypes.ts
@@ -322,3 +322,4 @@ export const VERIFIABLE_CREDENTIALS = "VERIFIABLE_CREDENTIALS";
export const CREATE_VC_WALLET_CONNECTION_INVITATION = "CREATE_VC_WALLET_CONNECTION_INVITATION";
export const FETCH_VC_WALLET_CONNECTION_INVITATIONS = "FETCH_VC_WALLET_CONNECTION_INVITATIONS";
export const ISSUE_VC = "ISSUE_VC";
+export const DELETE_VC_WALLET_CONNECTION = "DELETE_VC_WALLET_CONNECTION";
diff --git a/services/common/src/models/documents/document.ts b/services/common/src/models/documents/document.ts
index 9b85b6c842..4eb28c2337 100644
--- a/services/common/src/models/documents/document.ts
+++ b/services/common/src/models/documents/document.ts
@@ -163,10 +163,10 @@ export class MineDocument implements IMineDocument {
_userRoles: string[] = [],
is_latest_version: boolean = this.is_latest_version
) {
- const canModify = is_latest_version && !this.is_archived;
- if (!this.mine_document_guid) return [];
+ const canModify = is_latest_version && !this.is_archived && this.mine_document_guid;
+ const canView = this.file_type === ".pdf" && this.document_manager_guid;
return [
- this.file_type === ".pdf" && FileOperations.View,
+ canView && FileOperations.View,
FileOperations.Download,
isFeatureEnabled(Feature.DOCUMENTS_REPLACE_FILE) && canModify && FileOperations.Replace,
canModify && FileOperations.Archive,
diff --git a/services/common/src/redux/actionCreators/partiesActionCreator.ts b/services/common/src/redux/actionCreators/partiesActionCreator.ts
index 9a94d31d4c..9e89e2100b 100644
--- a/services/common/src/redux/actionCreators/partiesActionCreator.ts
+++ b/services/common/src/redux/actionCreators/partiesActionCreator.ts
@@ -298,6 +298,28 @@ export const createPartyOrgBookEntity = (
.finally(() => dispatch(hideLoading("modal")));
};
+export const deletePartyOrgBookEntity = (
+ partyGuid: string
+): AppThunk>> => (dispatch) => {
+ dispatch(request(reducerTypes.PARTY_ORGBOOK_ENTITY));
+ dispatch(showLoading("modal"));
+ return CustomAxios()
+ .delete(ENVIRONMENT.apiUrl + API.PARTY_ORGBOOK_ENTITY(partyGuid), createRequestHeader())
+ .then((response) => {
+ dispatch(hideLoading("modal"));
+ notification.success({
+ message: "Successfully disassociated party with OrgBook entity",
+ duration: 10,
+ });
+ dispatch(success(reducerTypes.PARTY_ORGBOOK_ENTITY));
+ return response;
+ })
+ .catch(() => {
+ dispatch(error(reducerTypes.PARTY_ORGBOOK_ENTITY));
+ })
+ .finally(() => dispatch(hideLoading("modal")));
+};
+
export const mergeParties = (payload: IMergeParties): AppThunk>> => (
dispatch
): Promise> => {
diff --git a/services/common/src/redux/actionCreators/verifiableCredentialActionCreator.ts b/services/common/src/redux/actionCreators/verifiableCredentialActionCreator.ts
index 544bd683a1..07260b3c76 100644
--- a/services/common/src/redux/actionCreators/verifiableCredentialActionCreator.ts
+++ b/services/common/src/redux/actionCreators/verifiableCredentialActionCreator.ts
@@ -93,3 +93,31 @@ export const fetchVCWalletInvitations = (
dispatch(hideLoading("modal"));
});
};
+
+export const deletePartyWalletConnection = (
+ partyGuid: string
+): AppThunk>> => (
+ dispatch
+): Promise> => {
+ dispatch(showLoading("modal"));
+ dispatch(request(reducerTypes.DELETE_VC_WALLET_CONNECTION));
+ return CustomAxios()
+ .delete(
+ `${ENVIRONMENT.apiUrl}/verifiable-credentials/${partyGuid}/connection/`,
+ createRequestHeader()
+ )
+ .then((response) => {
+ notification.success({
+ message: "Digital Wallet Connection Deleted",
+ description: "The user may establish a new connection through minespace",
+ duration: 10,
+ });
+ dispatch(success(reducerTypes.DELETE_VC_WALLET_CONNECTION));
+ dispatch(hideLoading("modal"));
+ return response;
+ })
+ .catch(() => {
+ dispatch(error(reducerTypes.DELETE_VC_WALLET_CONNECTION));
+ dispatch(hideLoading("modal"));
+ });
+};
diff --git a/services/common/src/redux/selectors/projectSelectors.ts b/services/common/src/redux/selectors/projectSelectors.ts
index 13a3473c5d..2d16c91a72 100644
--- a/services/common/src/redux/selectors/projectSelectors.ts
+++ b/services/common/src/redux/selectors/projectSelectors.ts
@@ -1,7 +1,7 @@
import { createSelector } from "reselect";
import { uniq } from "lodash";
import * as projectReducer from "../reducers/projectReducer";
-import { IParty, IProjectContact } from "../..";
+import { IParty, IProjectContact, IProjectSummaryDocument } from "../..";
import { getTransformedProjectSummaryAuthorizationTypes } from "./staticContentSelectors";
export const {
@@ -26,6 +26,21 @@ const formatProjectSummaryParty = (party): IParty => {
return { ...party, address: party.address[0] };
};
+const formatProjectSummaryDocuments = (documents = []): IProjectSummaryDocument[] => {
+ const allDocuments: any = { documents };
+ const fieldNameMap = {
+ support_documents: "SPR",
+ spatial_documents: "SPT",
+ };
+ Object.entries(fieldNameMap).forEach(([fieldName, docTypeCode]) => {
+ const matching = documents.filter(
+ (doc) => doc.project_summary_document_type_code === docTypeCode
+ );
+ allDocuments[fieldName] = matching;
+ });
+ return allDocuments;
+};
+
const formatProjectContact = (contacts): IProjectContact[] => {
if (!contacts) {
return contacts;
@@ -78,6 +93,7 @@ export const getAmsAuthorizationTypes = createSelector(
export const getFormattedProjectSummary = createSelector(
[getProjectSummary, getProject, getAmsAuthorizationTypes],
(summary, project, amsAuthTypes) => {
+ const documents = formatProjectSummaryDocuments(summary.documents);
const contacts = formatProjectContact(project.contacts);
const agent = formatProjectSummaryParty(summary.agent);
const facility_operator = formatProjectSummaryParty(summary.facility_operator);
@@ -90,6 +106,7 @@ export const getFormattedProjectSummary = createSelector(
facility_operator,
confirmation_of_submission,
...formatAuthorizations(amsAuthTypes, summary.status_code, summary.authorizations),
+ ...documents,
};
formattedSummary.project_lead_party_guid = project.project_lead_party_guid;
diff --git a/services/common/src/setupTests.ts b/services/common/src/setupTests.ts
index b287af258b..8388132111 100644
--- a/services/common/src/setupTests.ts
+++ b/services/common/src/setupTests.ts
@@ -13,6 +13,14 @@ Enzyme.configure({ adapter: new Adapter() });
setTimeout(callback, 0); // eslint-disable-line @typescript-eslint/no-implied-eval
};
+jest.mock("react", () => {
+ const original = jest.requireActual("react");
+ return {
+ ...original,
+ useLayoutEffect: jest.fn(),
+ };
+});
+
jest.mock("react-lottie", () => ({
__esModule: true,
default: "lottie-mock",
@@ -23,7 +31,7 @@ jest.mock("@mds/common/providers/featureFlags/useFeatureFlag", () => ({
isFeatureEnabled: () => true,
}),
}));
-
+window.scrollTo = jest.fn();
const location = JSON.stringify(window.location);
delete window.location;
diff --git a/services/core-api/app/api/parties/party/models/party.py b/services/core-api/app/api/parties/party/models/party.py
index ac49b0fdb8..3c00d24c49 100644
--- a/services/core-api/app/api/parties/party/models/party.py
+++ b/services/core-api/app/api/parties/party/models/party.py
@@ -77,21 +77,23 @@ class Party(SoftDeleteMixin, AuditMixin, Base):
uselist=False,
remote_side=[party_guid],
foreign_keys=[organization_guid])
-
+
digital_wallet_invitations = db.relationship(
'PartyVerifiableCredentialConnection',
lazy='select',
uselist=True,
+ primaryjoin=
+ "and_(PartyVerifiableCredentialConnection.party_guid == Party.party_guid, PartyVerifiableCredentialConnection.deleted_ind==False)",
order_by='desc(PartyVerifiableCredentialConnection.update_timestamp)',
overlaps='active_digital_wallet_connection')
-
+
active_digital_wallet_connection = db.relationship(
'PartyVerifiableCredentialConnection',
lazy='select',
uselist=False,
remote_side=[party_guid],
primaryjoin=
- 'and_(PartyVerifiableCredentialConnection.party_guid == Party.party_guid, PartyVerifiableCredentialConnection.connection_state==\'active\')',
+ 'and_(PartyVerifiableCredentialConnection.party_guid == Party.party_guid, PartyVerifiableCredentialConnection.deleted_ind==False, PartyVerifiableCredentialConnection.connection_state==\'active\')',
overlaps='digital_wallet_invitations')
@hybrid_property
diff --git a/services/core-api/app/api/parties/party/models/party_orgbook_entity.py b/services/core-api/app/api/parties/party/models/party_orgbook_entity.py
index 1249a897ae..e1bb8b65c5 100644
--- a/services/core-api/app/api/parties/party/models/party_orgbook_entity.py
+++ b/services/core-api/app/api/parties/party/models/party_orgbook_entity.py
@@ -23,7 +23,6 @@ class PartyOrgBookEntity(AuditMixin, Base):
association_user = db.Column(db.String, nullable=False, default=User().get_user_username)
association_timestamp = db.Column(db.DateTime, nullable=False, server_default=FetchedValue())
-
def __repr__(self):
return f'{self.__class__.__name__} {self.party_orgbook_entity_id}'
@@ -48,4 +47,7 @@ def create(cls, registration_id, registration_status, registration_date, name_id
party_guid=party_guid,
company_alias=company_alias)
party_orgbook_entity.save()
- return party_orgbook_entity
\ No newline at end of file
+ return party_orgbook_entity
+
+ def delete(self, commit=True):
+ super(PartyOrgBookEntity, self).delete(commit)
diff --git a/services/core-api/app/api/parties/party/resources/party_orgbook_entity_list_resource.py b/services/core-api/app/api/parties/party/resources/party_orgbook_entity_list_resource.py
index d4d5c927a8..5b0ae02a6c 100644
--- a/services/core-api/app/api/parties/party/resources/party_orgbook_entity_list_resource.py
+++ b/services/core-api/app/api/parties/party/resources/party_orgbook_entity_list_resource.py
@@ -4,7 +4,7 @@
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound, BadGateway
from app.extensions import api
-from app.api.utils.access_decorators import requires_role_edit_party
+from app.api.utils.access_decorators import requires_role_edit_party, requires_role_mine_admin
from app.api.utils.resources_mixins import UserMixin
from app.api.utils.custom_reqparser import CustomReqparser
from app.api.parties.party.models.party_orgbook_entity import PartyOrgBookEntity
@@ -59,3 +59,14 @@ def post(self, party_guid):
party.save()
return party_orgbook_entity, 201
+
+ @api.doc(description='Delete a Party OrgBook Entity.')
+ @requires_role_mine_admin
+ @api.marshal_with(PARTY_ORGBOOK_ENTITY, code=204)
+ def delete(self, party_guid):
+ party_orgbook_entity = PartyOrgBookEntity.find_by_party_guid(party_guid)
+ if party_orgbook_entity is None:
+ raise NotFound('OrgBook entity not found.')
+
+ party_orgbook_entity.delete()
+ return None, 204
diff --git a/services/core-api/app/api/projects/project_summary/models/project_summary.py b/services/core-api/app/api/projects/project_summary/models/project_summary.py
index 92d362c2d3..9da11785db 100644
--- a/services/core-api/app/api/projects/project_summary/models/project_summary.py
+++ b/services/core-api/app/api/projects/project_summary/models/project_summary.py
@@ -191,11 +191,8 @@ def proponent_project_id(self):
return None
@classmethod
- def find_by_project_summary_guid(cls, project_summary_guid, is_minespace_user=False):
- if is_minespace_user:
- return cls.query.filter_by(
- project_summary_guid=project_summary_guid, deleted_ind=False).one_or_none()
- return cls.query.filter(ProjectSummary.status_code.is_distinct_from("DFT")).filter_by(
+ def find_by_project_summary_guid(cls, project_summary_guid):
+ return cls.query.filter_by(
project_summary_guid=project_summary_guid, deleted_ind=False).one_or_none()
@classmethod
@@ -203,11 +200,8 @@ def find_by_mine_guid(cls, mine_guid_to_search):
return cls.query.filter(cls.mine_guid == mine_guid_to_search).all()
@classmethod
- def find_by_project_guid(cls, project_guid, is_minespace_user):
- if is_minespace_user:
- return cls.query.filter_by(project_guid=project_guid, deleted_ind=False).all()
- return cls.query.filter(ProjectSummary.status_code.is_distinct_from("DFT")).filter_by(
- project_guid=project_guid, deleted_ind=False).all()
+ def find_by_project_guid(cls, project_guid):
+ return cls.query.filter_by(project_guid=project_guid, deleted_ind=False).all()
@classmethod
def find_by_mine_document_guid(cls, mine_document_guid):
diff --git a/services/core-api/app/api/projects/project_summary/resources/project_summary.py b/services/core-api/app/api/projects/project_summary/resources/project_summary.py
index 8ee0cb24ce..5fa355bfc5 100644
--- a/services/core-api/app/api/projects/project_summary/resources/project_summary.py
+++ b/services/core-api/app/api/projects/project_summary/resources/project_summary.py
@@ -184,8 +184,7 @@ class ProjectSummaryResource(Resource, UserMixin):
@requires_any_of([VIEW_ALL, MINESPACE_PROPONENT])
@api.marshal_with(PROJECT_SUMMARY_MODEL, code=200)
def get(self, project_guid, project_summary_guid):
- project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid,
- is_minespace_user())
+ project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid)
if project_summary is None:
raise NotFound('Project Description not found')
@@ -200,8 +199,7 @@ def get(self, project_guid, project_summary_guid):
@requires_any_of([MINE_ADMIN, MINESPACE_PROPONENT, EDIT_PROJECT_SUMMARIES])
@api.marshal_with(PROJECT_SUMMARY_MODEL, code=200)
def put(self, project_guid, project_summary_guid):
- project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid,
- is_minespace_user())
+ project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid)
project = Project.find_by_project_guid(project_guid)
data = self.parser.parse_args()
@@ -283,8 +281,7 @@ def put(self, project_guid, project_summary_guid):
@requires_any_of([MINE_ADMIN, MINESPACE_PROPONENT, EDIT_PROJECT_SUMMARIES])
@api.response(204, 'Successfully deleted.')
def delete(self, project_guid, project_summary_guid):
- project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid,
- is_minespace_user())
+ project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid)
if project_summary is None:
raise NotFound('Project Description not found')
diff --git a/services/core-api/app/api/projects/project_summary/resources/project_summary_list.py b/services/core-api/app/api/projects/project_summary/resources/project_summary_list.py
index 2177281b3c..2fe0698adc 100644
--- a/services/core-api/app/api/projects/project_summary/resources/project_summary_list.py
+++ b/services/core-api/app/api/projects/project_summary/resources/project_summary_list.py
@@ -35,7 +35,7 @@ def get(self, project_guid):
project_summaries = []
for project in projects:
project_project_summaries = ProjectSummary.find_by_project_guid(
- project.project_guid, is_minespace_user())
+ project.project_guid)
project_summaries = [*project_summaries, *project_project_summaries]
return project_summaries
diff --git a/services/core-api/app/api/services/traction_service.py b/services/core-api/app/api/services/traction_service.py
index e91a5b571b..8122c8ae26 100644
--- a/services/core-api/app/api/services/traction_service.py
+++ b/services/core-api/app/api/services/traction_service.py
@@ -9,6 +9,7 @@
traction_token_url = Config.TRACTION_HOST + "/multitenancy/tenant/" + Config.TRACTION_TENANT_ID + "/token"
traction_oob_create_invitation = Config.TRACTION_HOST + "/out-of-band/create-invitation"
+traction_connections = Config.TRACTION_HOST + "/connections"
traction_offer_credential = Config.TRACTION_HOST + "/issue-credential/send-offer"
revoke_credential_url = Config.TRACTION_HOST + "/revocation/revoke"
fetch_credential_exchanges = Config.TRACTION_HOST + "/issue-credential/records"
@@ -84,6 +85,12 @@ def create_oob_connection_invitation(self, party: Party):
return response
+ def delete_connection(self, connection_id) -> bool:
+ revoke_resp = requests.delete(
+ traction_connections + "/" + str(connection_id), headers=self.get_headers())
+ assert revoke_resp.status_code == 200, f"revoke_resp={revoke_resp.json()}"
+ return True
+
def offer_mines_act_permit_111(self, connection_id, attributes):
# https://github.com/bcgov/bc-vcpedia/blob/main/credentials/bc-mines-act-permit/1.1.1/governance.md#261-schema-definition
payload = {
diff --git a/services/core-api/app/api/verifiable_credentials/models/connection.py b/services/core-api/app/api/verifiable_credentials/models/connection.py
index 01a43b339a..a4fa506a35 100644
--- a/services/core-api/app/api/verifiable_credentials/models/connection.py
+++ b/services/core-api/app/api/verifiable_credentials/models/connection.py
@@ -1,12 +1,11 @@
-
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.schema import FetchedValue
from app.extensions import db
-from app.api.utils.models_mixins import AuditMixin, Base
+from app.api.utils.models_mixins import AuditMixin, Base, SoftDeleteMixin
-class PartyVerifiableCredentialConnection(AuditMixin, Base):
+class PartyVerifiableCredentialConnection(AuditMixin, SoftDeleteMixin, Base):
"""Verificable Credential reference to Traction, a Multi-tenant Hyperledger Aries Wallet"""
__tablename__ = "party_verifiable_credential_connection"
invitation_id = db.Column(db.String, primary_key=True)
@@ -14,38 +13,40 @@ class PartyVerifiableCredentialConnection(AuditMixin, Base):
party_guid = db.Column(UUID(as_uuid=True), db.ForeignKey('party.party_guid'), nullable=False)
connection_id = db.Column(db.String)
- connection_state = db.Column(db.String, server_default=FetchedValue())
+ connection_state = db.Column(db.String, server_default=FetchedValue())
#ARIES-RFC 0023 https://github.com/hyperledger/aries-rfcs/tree/main/features/0023-did-exchange
last_webhook_timestamp = db.Column(db.DateTime, nullable=True)
-
def __repr__(self):
- return '' % (self.party_guid, self.connection_state)
+ return '' % (
+ self.party_guid, self.connection_state)
def json(self):
return {
- "connection_id":self.connection_id,
- "connection_state":self.connection_state,
+ "connection_id": self.connection_id,
+ "connection_state": self.connection_state,
"update_timestamp": self.update_timestamp,
}
@classmethod
def find_by_party_guid(cls, party_guid) -> "PartyVerifiableCredentialConnection":
- return cls.query.filter_by(party_guid=party_guid).all()
-
+ return cls.query.filter_by(party_guid=party_guid, deleted_ind=False).all()
+
@classmethod
def find_active_by_party_guid(cls, party_guid) -> "PartyVerifiableCredentialConnection":
- return cls.query.filter_by(party_guid=party_guid, connection_state="active").first()
-
+ return cls.query.filter_by(
+ party_guid=party_guid, connection_state="active", deleted_ind=False).first()
+
@classmethod
def find_by_invitation_id(cls, invitation_id) -> "PartyVerifiableCredentialConnection":
- return cls.query.filter_by(invitation_id=invitation_id).one_or_none()
-
+ return cls.query.filter_by(invitation_id=invitation_id, deleted_ind=False).one_or_none()
+
@classmethod
def find_by_connection_id(cls, connection_id) -> "PartyVerifiableCredentialConnection":
- return cls.query.filter_by(connection_id=connection_id).one_or_none()
-
+ return cls.query.filter_by(connection_id=connection_id, deleted_ind=False).one_or_none()
+
@classmethod
def find_active_by_invitation_id(cls, invitation_id) -> "PartyVerifiableCredentialConnection":
- return cls.query.filter_by(invitation_id=invitation_id, connection_state="active").one_or_none()
-
\ No newline at end of file
+ return cls.query.filter_by(
+ invitation_id=invitation_id, connection_state="active",
+ deleted_ind=False).one_or_none()
diff --git a/services/core-api/app/api/verifiable_credentials/namespace.py b/services/core-api/app/api/verifiable_credentials/namespace.py
index a20ef3a0b9..b39d519d7a 100644
--- a/services/core-api/app/api/verifiable_credentials/namespace.py
+++ b/services/core-api/app/api/verifiable_credentials/namespace.py
@@ -1,19 +1,20 @@
from flask_restx import Namespace
-from app.api.verifiable_credentials.resources.verifiable_credential import VerifiableCredentialResource
-from app.api.verifiable_credentials.resources.verifiable_credential_connections import VerifiableCredentialConnectionResource
-from app.api.verifiable_credentials.resources.verifiable_credential_webhook import VerifiableCredentialWebhookResource
-from app.api.verifiable_credentials.resources.verifiable_credential_map import VerifiableCredentialMinesActPermitResource
-from app.api.verifiable_credentials.resources.verifiable_credential_map_detail import VerifiableCredentialCredentialExchangeResource
-from app.api.verifiable_credentials.resources.verifiable_credential_revocation import VerifiableCredentialRevocationResource
+from app.api.verifiable_credentials.resources.vc_connections import VerifiableCredentialConnectionResource
+from app.api.verifiable_credentials.resources.vc_connection_invitations import VerifiableCredentialConnectionInvitationsResource
+from app.api.verifiable_credentials.resources.traction_webhook import TractionWebhookResource
+from app.api.verifiable_credentials.resources.vc_map import VerifiableCredentialMinesActPermitResource
+from app.api.verifiable_credentials.resources.vc_map_detail import VerifiableCredentialCredentialExchangeResource
+from app.api.verifiable_credentials.resources.vc_revocation import VerifiableCredentialRevocationResource
from app.api.verifiable_credentials.resources.w3c_map_credential_resource import W3CCredentialResource, W3CCredentialListResource, W3CCredentialDeprecatedResource
api = Namespace('verifiable-credentials', description='Variances actions/options')
-api.add_resource(VerifiableCredentialResource, '')
-api.add_resource(VerifiableCredentialWebhookResource, '/webhook/topic//')
+api.add_resource(TractionWebhookResource, '/webhook/topic//')
-api.add_resource(VerifiableCredentialConnectionResource, '//oob-invitation')
+api.add_resource(VerifiableCredentialConnectionInvitationsResource,
+ '//oob-invitation')
+api.add_resource(VerifiableCredentialConnectionResource, '//connection/')
api.add_resource(VerifiableCredentialMinesActPermitResource,
'//mines-act-permits')
api.add_resource(VerifiableCredentialCredentialExchangeResource,
diff --git a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_webhook.py b/services/core-api/app/api/verifiable_credentials/resources/traction_webhook.py
similarity index 73%
rename from services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_webhook.py
rename to services/core-api/app/api/verifiable_credentials/resources/traction_webhook.py
index 52f039f768..388680a242 100644
--- a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_webhook.py
+++ b/services/core-api/app/api/verifiable_credentials/resources/traction_webhook.py
@@ -23,16 +23,18 @@
PING = "ping"
ISSUER_CREDENTIAL_REVOKED = "issuer_cred_rev"
-class VerifiableCredentialWebhookResource(Resource, UserMixin):
+
+class TractionWebhookResource(Resource, UserMixin):
+
@api.doc(description='Endpoint to recieve webhooks from Traction.', params={})
def post(self, topic):
if not is_feature_enabled(Feature.TRACTION_VERIFIABLE_CREDENTIALS):
raise NotImplemented()
-
+
#custom auth for traction
if request.headers.get("x-api-key") != Config.TRACTION_WEBHOOK_X_API_KEY:
- return Forbidden("bad x-api-key")
-
+ return Forbidden("bad x-api-key")
+
webhook_body = request.get_json()
current_app.logger.debug(f"webhook received : {webhook_body}")
if "updated_at" not in webhook_body:
@@ -42,7 +44,8 @@ def post(self, topic):
if topic == CONNECTIONS:
invitation_id = webhook_body['invitation_msg_id']
- vc_conn = PartyVerifiableCredentialConnection.query.unbound_unsafe().filter_by(invitation_id=invitation_id).first()
+ vc_conn = PartyVerifiableCredentialConnection.query.unbound_unsafe().filter_by(
+ invitation_id=invitation_id).first()
assert vc_conn, f"connection.invitation_msg_id={invitation_id} not found. webhook_body={webhook_body}"
if not vc_conn.connection_id:
vc_conn.connection_id = webhook_body["connection_id"]
@@ -52,55 +55,66 @@ def post(self, topic):
# already processed a more recent webhook
else:
vc_conn.last_webhook_timestamp = webhook_timestamp
-
+
new_state = webhook_body["state"]
if new_state != vc_conn.connection_state and vc_conn.connection_state != DIDExchangeRequesterState.COMPLETED:
# 'completed' is the final succesful state.
- vc_conn.connection_state=new_state
+ vc_conn.connection_state = new_state
vc_conn.save()
- current_app.logger.info(f"Updated party_vc_conn connection_id={vc_conn.connection_id} with state={new_state}")
+ current_app.logger.info(
+ f"Updated party_vc_conn connection_id={vc_conn.connection_id} with state={new_state}"
+ )
if new_state == "deleted":
# if deleted in the wallet (either in traction, or by the other agent)
- vc_conn.connection_state=new_state
- vc_conn.save()
- current_app.logger.info(f"party_vc_conn connection_id={vc_conn.connection_id} was deleted")
-
+ vc_conn.connection_state = new_state
+ vc_conn.save()
+ current_app.logger.info(
+ f"party_vc_conn connection_id={vc_conn.connection_id} was deleted")
+
elif topic == OUT_OF_BAND:
- current_app.logger.info(f"out-of-band message invi_msg_id={webhook_body['invi_msg_id']}, state={webhook_body['state']}")
-
+ current_app.logger.info(
+ f"out-of-band message invi_msg_id={webhook_body['invi_msg_id']}, state={webhook_body['state']}"
+ )
+
elif topic == CREDENTIAL_OFFER:
cred_exch_id = webhook_body["credential_exchange_id"]
- cred_exch_record = PartyVerifiableCredentialMinesActPermit.query.unbound_unsafe().filter_by(cred_exch_id=cred_exch_id).first()
+ cred_exch_record = PartyVerifiableCredentialMinesActPermit.query.unbound_unsafe(
+ ).filter_by(cred_exch_id=cred_exch_id).first()
assert cred_exch_record, f"issue_credential.credential_exchange_id={cred_exch_id} not found. webhook_body={webhook_body}"
new_state = webhook_body["state"]
if cred_exch_record.last_webhook_timestamp and cred_exch_record.last_webhook_timestamp >= webhook_timestamp:
current_app.logger.warn(f"webhooks out of order catch, ignoring {webhook_body}")
- # already processed a more recent webhook
+ # already processed a more recent webhook
else:
cred_exch_record.last_webhook_timestamp = webhook_timestamp
if new_state == IssueCredentialIssuerState.ABANDONED:
- current_app.logger.warning(f"cred_exch_id={cred_exch_id} is abanoned with message = {webhook_body['error_msg']}")
+ current_app.logger.warning(
+ f"cred_exch_id={cred_exch_id} is abanoned with message = {webhook_body['error_msg']}"
+ )
cred_exch_record.error_description = webhook_body['error_msg']
-
- cred_exch_record.cred_exch_state=new_state
+
+ cred_exch_record.cred_exch_state = new_state
if new_state == IssueCredentialIssuerState.CREDENTIAL_ACKED:
cred_exch_record.rev_reg_id = webhook_body["revoc_reg_id"]
cred_exch_record.cred_rev_id = webhook_body["revocation_id"]
cred_exch_record.save()
- current_app.logger.info(f"Updated cred_exch_record cred_exch_id={cred_exch_id} with state={new_state}")
+ current_app.logger.info(
+ f"Updated cred_exch_record cred_exch_id={cred_exch_id} with state={new_state}")
elif topic == ISSUER_CREDENTIAL_REVOKED:
if webhook_body["state"] != "revoked":
- current_app.logger.info(f"CREDENTIAL SUCCESSFULLY REVOKED received={request.get_json()}")
- cred_exch = PartyVerifiableCredentialMinesActPermit.find_by_cred_exch_id(webhook_body["cred_ex_id"], unsafe=True)
+ current_app.logger.info(
+ f"CREDENTIAL SUCCESSFULLY REVOKED received={request.get_json()}")
+ cred_exch = PartyVerifiableCredentialMinesActPermit.find_by_cred_exch_id(
+ webhook_body["cred_ex_id"], unsafe=True)
cred_exch.permit_amendment.permit.mines_act_permit_vc_locked = True
cred_exch.save()
-
+
elif topic == PING:
current_app.logger.info(f"TrustPing received={request.get_json()}")
-
+
else:
- current_app.logger.info(f"unknown topic '{topic}', webhook_body={webhook_body}")
+ current_app.logger.info(f"unknown topic '{topic}', webhook_body={webhook_body}")
diff --git a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_connections.py b/services/core-api/app/api/verifiable_credentials/resources/vc_connection_invitations.py
similarity index 71%
rename from services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_connections.py
rename to services/core-api/app/api/verifiable_credentials/resources/vc_connection_invitations.py
index 27dbce6884..99ebb79980 100644
--- a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_connections.py
+++ b/services/core-api/app/api/verifiable_credentials/resources/vc_connection_invitations.py
@@ -11,7 +11,9 @@
from app.api.utils.resources_mixins import UserMixin
from app.api.utils.feature_flag import Feature, is_feature_enabled
-class VerifiableCredentialConnectionResource(Resource, UserMixin):
+
+class VerifiableCredentialConnectionInvitationsResource(Resource, UserMixin):
+
@api.doc(description='Create a connection invitation for a party by guid', params={})
@requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
def post(self, party_guid: str):
@@ -20,12 +22,11 @@ def post(self, party_guid: str):
party = Party.find_by_party_guid(party_guid)
if not party:
raise NotFound(f"party not found with party_guid {party_guid}")
-
+
traction_svc = TractionService()
invitation = traction_svc.create_oob_connection_invitation(party)
-
+
return invitation
-
@api.doc(description='Create a connection invitation for a party by guid', params={})
@requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
@@ -33,6 +34,17 @@ def post(self, party_guid: str):
def get(self, party_guid: str):
if not is_feature_enabled(Feature.TRACTION_VERIFIABLE_CREDENTIALS):
raise NotImplemented()
- party_vc_conn = PartyVerifiableCredentialConnection.find_by_party_guid(party_guid=party_guid)
+ party_vc_conn = PartyVerifiableCredentialConnection.find_by_party_guid(
+ party_guid=party_guid)
return party_vc_conn
-
\ No newline at end of file
+
+ @api.doc(description="Delete a connection for a party by guid", params={})
+ @requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
+ def delete(self, party_guid):
+ if not is_feature_enabled(Feature.TRACTION_VERIFIABLE_CREDENTIALS):
+ raise NotImplemented()
+ party_vc_conn = PartyVerifiableCredentialConnection.find_by_party_guid(party_guid)
+ if not party_vc_conn:
+ raise NotFound(f"party_vc_conn not found with party_guid {party_guid}")
+ party_vc_conn.delete()
+ party_vc_conn.save()
diff --git a/services/core-api/app/api/verifiable_credentials/resources/vc_connections.py b/services/core-api/app/api/verifiable_credentials/resources/vc_connections.py
new file mode 100644
index 0000000000..d9730668dd
--- /dev/null
+++ b/services/core-api/app/api/verifiable_credentials/resources/vc_connections.py
@@ -0,0 +1,34 @@
+from flask import current_app
+from flask_restx import Resource
+from werkzeug.exceptions import BadRequest
+from app.extensions import api
+from app.api.utils.access_decorators import requires_any_of, EDIT_PARTY, MINESPACE_PROPONENT
+
+from app.api.parties.party.models.party import Party
+from app.api.verifiable_credentials.models.connection import PartyVerifiableCredentialConnection
+from app.api.services.traction_service import TractionService
+from app.api.verifiable_credentials.response_models import PARTY_VERIFIABLE_CREDENTIAL_CONNECTION
+from app.api.utils.resources_mixins import UserMixin
+from app.api.utils.feature_flag import Feature, is_feature_enabled
+
+
+class VerifiableCredentialConnectionResource(Resource, UserMixin):
+
+ @api.doc(description='Delete a connection party by connection_id', params={})
+ @requires_any_of([EDIT_PARTY, MINESPACE_PROPONENT])
+ def delete(self, party_guid: str):
+
+ active_conn = PartyVerifiableCredentialConnection.find_active_by_party_guid(party_guid)
+ conns = PartyVerifiableCredentialConnection.find_by_party_guid(party_guid)
+ current_app.logger.warning(conns)
+ if not active_conn:
+ raise BadRequest(f"party has no active connection party_guid={party_guid}")
+
+ # this will hard delete the didcomm connection, but will maintain the CORE records of anything that was exchanged on that connection
+ active_conn.delete()
+
+ t_service = TractionService()
+ delete_success = t_service.delete_connection(active_conn.connection_id)
+ assert delete_success
+
+ active_conn.save()
diff --git a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_map.py b/services/core-api/app/api/verifiable_credentials/resources/vc_map.py
similarity index 100%
rename from services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_map.py
rename to services/core-api/app/api/verifiable_credentials/resources/vc_map.py
diff --git a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_map_detail.py b/services/core-api/app/api/verifiable_credentials/resources/vc_map_detail.py
similarity index 100%
rename from services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_map_detail.py
rename to services/core-api/app/api/verifiable_credentials/resources/vc_map_detail.py
diff --git a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_revocation.py b/services/core-api/app/api/verifiable_credentials/resources/vc_revocation.py
similarity index 100%
rename from services/core-api/app/api/verifiable_credentials/resources/verifiable_credential_revocation.py
rename to services/core-api/app/api/verifiable_credentials/resources/vc_revocation.py
diff --git a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential.py b/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential.py
deleted file mode 100644
index 8cb79af296..0000000000
--- a/services/core-api/app/api/verifiable_credentials/resources/verifiable_credential.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from flask_restx import Resource
-
-from app.extensions import api
-from app.api.utils.access_decorators import requires_any_of, MINESPACE_PROPONENT, VIEW_ALL
-
-from app.api.utils.resources_mixins import UserMixin
-from app.api.services.traction_service import TractionService
-
-class VerifiableCredentialResource(Resource, UserMixin):
- @api.doc(description='test authorization with traction')
- @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT])
- def get(self):
- traction_svc=TractionService()
- return {"sample_token":traction_svc.token}
diff --git a/services/core-api/tests/projects/project_summaries/models/test_project_summary_model.py b/services/core-api/tests/projects/project_summaries/models/test_project_summary_model.py
index a79d746357..d951138098 100644
--- a/services/core-api/tests/projects/project_summaries/models/test_project_summary_model.py
+++ b/services/core-api/tests/projects/project_summaries/models/test_project_summary_model.py
@@ -6,14 +6,14 @@
def test_project_summary_find_by_project_summary_guid(db_session):
project_summary = ProjectSummaryFactory()
project_summary_guid = project_summary.project_summary_guid
- project_summary = ProjectSummary.find_by_project_summary_guid(str(project_summary_guid), True)
+ project_summary = ProjectSummary.find_by_project_summary_guid(str(project_summary_guid))
assert project_summary.project_summary_guid == project_summary_guid
def test_project_summary_find_by_project_guid(db_session):
batch_size = 1
project = ProjectFactory.create_batch(size=batch_size)
- project_summaries = ProjectSummary.find_by_project_guid(str(project[0].project_guid), True)
+ project_summaries = ProjectSummary.find_by_project_guid(str(project[0].project_guid))
assert len(project_summaries) == batch_size
assert all(project_summary.project_guid == project[0].project_guid
diff --git a/services/core-web/cypress/e2e/createproject.cy.ts b/services/core-web/cypress/e2e/createproject.cy.ts
index 9285549ec1..93d93419e0 100644
--- a/services/core-web/cypress/e2e/createproject.cy.ts
+++ b/services/core-web/cypress/e2e/createproject.cy.ts
@@ -35,17 +35,142 @@ describe("Major Projects", () => {
cy.get("#project_summary_description").type("This is just a Cypress test project description", {
force: true,
});
- cy.get("#contacts\\[0\\]\\.first_name").type("Cypress", { force: true });
- cy.get("#contacts\\[0\\]\\.last_name").type("Test", { force: true });
- cy.get("#contacts\\[0\\]\\.email").type("cypress@mds.com", { force: true });
- cy.get("#contacts\\[0\\]\\.phone_number").type("1234567890", { force: true });
+
+ // SAVE & CONTINUE - skip to Purpose & Authorization
+ cy.contains("Save & Continue").click({ force: true });
+ cy.contains("Project Lead", { timeout: 10000 });
+ cy.contains("Purpose and Authorization", { timeout: 10000 }).click({ force: true });
+ cy.contains("Regulatory Approval Type", { timeout: 10000 });
+
cy.get('[data-cy="checkbox-authorization-OTHER"]').click({ force: true });
cy.get(
'[name="authorizations.OTHER[0].authorization_description"]'
).type("legislation description", { force: true });
+ // SAVE & CONTINUE - direct to Project Contacts
+ cy.contains("Save & Continue").click({ force: true });
+ cy.contains("First Name", { timeout: 10000 });
+
+ cy.get(`[name="contacts[0].first_name"]`).type("Cypress", { force: true });
+ cy.get(`[name="contacts[0].last_name"]`).type("Test", { force: true });
+ cy.get(`[name="contacts[0].email"]`).type("cypress@mds.com", { force: true });
+ cy.get(`[name="contacts[0].phone_number"]`).type("1234567890", { force: true });
+ cy.get(`[name="contacts[0].address.address_line_1"]`).type("123 Fake St", { force: true });
+ cy.contains("Please select")
+ .first()
+ .click({
+ force: true,
+ });
+ cy.get('[title="Canada"]').click({ force: true });
+ cy.get(`[name="contacts[0].address.city"]`).type("Cityville", { force: true });
+ cy.contains("Please select")
+ .first()
+ .click({
+ force: true,
+ });
+ cy.get('[title="AB"]').click({ force: true });
+ cy.get(`[name="contacts[0].address.post_code"]`).type("A0A0A0", { force: true });
+
+ // SAVE & CONTINUE - Applicant Information
+ cy.contains("Save & Continue").click({ force: true });
+ cy.contains("Applicant Information", { timeout: 10000 });
+
+ cy.contains("Individual").click({ force: true });
+ cy.get(`[name="applicant.first_name"]`).type("Cypress", { force: true });
+ cy.get(`[name="applicant.party_name"]`).type("Test", { force: true });
+ cy.get(`[name="applicant.phone_no"]`).type("1231231234", { force: true });
+ cy.get(`[name="applicant.email"]`).type("email@email.com", { force: true });
+ cy.get(`[name="applicant.address[0].address_line_1"]`).type("123 Fake St", { force: true });
+ cy.get(`[data-cy="applicant.address[0].address_type_code"]`)
+ .contains("Please select")
+ .click({
+ force: true,
+ });
+ cy.get('[title="Canada"]').click({ force: true });
+ cy.get(`[data-cy="applicant.address[0].sub_division_code"]`)
+ .contains("Please select")
+ .click({
+ force: true,
+ });
+ cy.get('[title="AB"]').click({ force: true });
+ cy.get(`[name="applicant.address[0].post_code"]`).type("A0A0A0", { force: true });
+ cy.get(`[name="applicant.address[0].city"]`).type("Cityville", { force: true });
+ cy.contains("Same as mailing address")
+ .first()
+ .click({ force: true });
+ cy.contains("Same as legal address")
+ .first()
+ .click({ force: true });
+
+ // SAVE & CONTINUE - Agent
+ cy.contains("Save & Continue").click({ force: true });
+ cy.contains("Are you an agent applying on behalf of the applicant?", { timeout: 10000 });
+
+ cy.contains("No").click({ force: true });
+
+ // SAVE & CONTINUE - Location, Access and Land Use
+ cy.contains("Save & Continue").click({ force: true });
+ cy.scrollTo(0, 0);
+ cy.get(`[name="is_legal_land_owner"]`, { timeout: 10000 })
+ .first()
+ .scrollIntoView()
+ .click({ force: true }); // click yes
+ cy.get(`[name="facility_latitude"]`).type("48", { force: true });
+ cy.get(`[name="facility_longitude"]`).type("-114", { force: true });
+
+ cy.get(`[data-cy="facility_coords_source"]`)
+ .contains("Please select")
+ .click({ force: true });
+ cy.get('[title="GPS"]').click({ force: true });
+ cy.get(`[data-cy="nearest_municipality"]`)
+ .contains("Please select")
+ .click({ force: true });
+ cy.get('[title="Abbotsford"]').click({ force: true });
+
+ cy.get(`[name="facility_pid_pin_crown_file_no"]`).type("123", { force: true });
+ cy.get(`[name="facility_lease_no"]`).type("456", { force: true });
+
+ // SAVE & CONTINUE - Mine Components and Offsite Infrastructure
+ cy.contains("Save & Continue").click({ force: true });
+ cy.contains("Facility Type", { timeout: 10000 });
+ cy.get(`[name="facility_type"]`).type("facility type", { force: true });
+ cy.get(`[name="facility_desc"]`).type("facility description", { force: true });
+
+ cy.get(`[data-cy="regional_district_id"]`)
+ .contains("Please select")
+ .click({ force: true });
+ cy.get('[title="Cariboo"]').click({ force: true });
+
+ cy.get(`[name="facility_operator.address.address_line_1"]`).type("123 Fake St", {
+ force: true,
+ });
+ cy.get(`[name="facility_operator.address.city"]`).type("Cityville", { force: true });
+
+ cy.get(`[data-cy="facility_operator.address.sub_division_code"]`)
+ .contains("Please select")
+ .click({
+ force: true,
+ });
+ cy.get('[title="AB"]').click({ force: true });
+ cy.get(`[name="zoning"]`, { timeout: 10000 })
+ .first()
+ .click(); // click yes
+
+ cy.get(`[name="facility_operator.first_name"]`).type("Firstname", { force: true });
+ cy.get(`[name="facility_operator.party_name"]`).type("Lastname", { force: true });
+ cy.get(`[name="facility_operator.phone_no"]`).type("1231231234", { force: true });
+
+ // SAVE & CONTINUE - skip to Declaration
+ cy.contains("Save & Continue").click({ force: true });
+ cy.get("#expected_draft_irt_submission_date", { timeout: 10000 });
+ cy.contains("Declaration", { timeout: 10000 }).click({ force: true });
+
+ cy.get("input#ADD_EDIT_PROJECT_SUMMARY_confirmation_of_submission", { timeout: 10000 }).click({
+ force: true,
+ });
+
// Submit the project
- cy.get('[data-cy="project-summary-submit-button"]').click({ force: true });
+ cy.contains("Submit").click({ force: true });
// wait for API to respond before navigating
cy.wait(15000);
// Navigate back to projects
diff --git a/services/core-web/cypress/e2e/majorprojects.cy.ts b/services/core-web/cypress/e2e/majorprojects.cy.ts
index 95d1dbba51..57a6c8581b 100644
--- a/services/core-web/cypress/e2e/majorprojects.cy.ts
+++ b/services/core-web/cypress/e2e/majorprojects.cy.ts
@@ -18,9 +18,9 @@ describe("Major Projects", () => {
it("should upload and download a document successfully", () => {
const fileName = "dummy.pdf";
- cy.get("#project-summary-submit").then(($button) => {
- $button[0].click();
- });
+ cy.contains("Document Upload").click();
+
+ cy.contains("Edit Project Description").click();
cy.fixture(fileName).then((fileContent) => {
// Intercept the POST request and stub the response
@@ -63,11 +63,13 @@ describe("Major Projects", () => {
}
).as("statusRequest");
- cy.get('input[type="file"]').attachFile({
- fileContent: fileContent,
- fileName: fileName,
- mimeType: "application/pdf",
- });
+ cy.get('input[type="file"]')
+ .eq(1)
+ .attachFile({
+ fileContent: fileContent,
+ fileName: fileName,
+ mimeType: "application/pdf",
+ });
// Wait for the upload request to complete(simulated)
cy.wait("@uploadRequest").then((interception) => {
diff --git a/services/core-web/src/components/Forms/parties/PartyOrgBookForm.tsx b/services/core-web/src/components/Forms/parties/PartyOrgBookForm.tsx
index 2dbb69ce17..4fd1144824 100644
--- a/services/core-web/src/components/Forms/parties/PartyOrgBookForm.tsx
+++ b/services/core-web/src/components/Forms/parties/PartyOrgBookForm.tsx
@@ -6,11 +6,14 @@ import { isEmpty } from "lodash";
import { useDispatch } from "react-redux";
import {
createPartyOrgBookEntity,
+ deletePartyOrgBookEntity,
fetchPartyById,
} from "@mds/common/redux/actionCreators/partiesActionCreator";
import { ORGBOOK_ENTITY_URL } from "@/constants/routes";
import { IOrgbookCredential, IParty } from "@mds/common";
import OrgBookSearch from "@mds/common/components/parties/OrgBookSearch";
+import * as Permission from "@/constants/permissions";
+import AuthorizationWrapper from "@/components/common/wrappers/AuthorizationWrapper";
interface PartyOrgBookFormProps {
party: IParty;
@@ -20,6 +23,8 @@ export const PartyOrgBookForm: FC = ({ party }) => {
const [isAssociating, setIsAssociating] = useState(false);
const dispatch = useDispatch();
const [credential, setCredential] = useState(null);
+ const [currentParty, setCurrentParty] = useState(party.party_orgbook_entity.name_text);
+ const [isAssociated, setIsAssociated] = useState(!!party.party_orgbook_entity.name_text);
const handleAssociateButtonClick = async () => {
setIsAssociating(true);
@@ -31,6 +36,17 @@ export const PartyOrgBookForm: FC = ({ party }) => {
await dispatch(fetchPartyById(party.party_guid));
setIsAssociating(false);
+ setIsAssociated(true);
+ };
+
+ const handleDisassociateButtonClick = async () => {
+ setIsAssociating(true);
+
+ await dispatch(deletePartyOrgBookEntity(party.party_guid));
+ await dispatch(fetchPartyById(party.party_guid));
+ setIsAssociating(false);
+ setCurrentParty("");
+ setIsAssociated(false);
};
const hasOrgBookCredential = !isEmpty(credential);
@@ -38,7 +54,11 @@ export const PartyOrgBookForm: FC = ({ party }) => {
return (
-
+
= ({ party }) => {
View on OrgBook
-
-
-
- Associate
-
-
+
+
+
+
+ {!isAssociated ? "Associate" : "Disassociate"}
+
+
+
);
diff --git a/services/core-web/src/components/Forms/projectSummaries/ProjectSummaryForm.tsx b/services/core-web/src/components/Forms/projectSummaries/ProjectSummaryForm.tsx
deleted file mode 100644
index cb8ba551dd..0000000000
--- a/services/core-web/src/components/Forms/projectSummaries/ProjectSummaryForm.tsx
+++ /dev/null
@@ -1,551 +0,0 @@
-import React, { FC, useState } from "react";
-import { compose } from "redux";
-import { connect, useSelector } from "react-redux";
-import { useHistory, useParams, withRouter } from "react-router-dom";
-import {
- FieldArray,
- Field,
- reduxForm,
- formValueSelector,
- InjectedFormProps,
- change,
-} from "redux-form";
-import { Alert, Button, Row, Col, Typography, Popconfirm, Form } from "antd";
-import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
-import {
- maxLength,
- phoneNumber,
- required,
- email,
- dateNotBeforeOther,
- dateNotAfterOther,
-} from "@common/utils/Validate";
-import {
- getDropdownProjectSummaryStatusCodes,
- getProjectSummaryDocumentTypesHash,
-} from "@mds/common/redux/selectors/staticContentSelectors";
-import AuthorizationsInvolved from "@mds/common/components/projectSummary/AuthorizationsInvolved";
-import { getDropdownProjectLeads } from "@mds/common/redux/selectors/partiesSelectors";
-import { getUserAccessData } from "@mds/common/redux/selectors/authenticationSelectors";
-import { Feature, IGroupedDropdownList, IProject, IProjectSummary, USER_ROLES } from "@mds/common";
-import { normalizePhone } from "@common/utils/helpers";
-import * as FORM from "@/constants/forms";
-import * as routes from "@/constants/routes";
-import { renderConfig } from "@/components/common/config";
-import LinkButton from "@/components/common/buttons/LinkButton";
-import { ProjectSummaryDocumentUpload } from "@/components/Forms/projectSummaries/ProjectSummaryDocumentUpload";
-import ArchivedDocumentsSection from "@common/components/documents/ArchivedDocumentsSection";
-import { MajorMineApplicationDocument } from "@mds/common/models/documents/document";
-import ProjectLinks from "@mds/common/components/projects/ProjectLinks";
-import { useFeatureFlag } from "@mds/common/providers/featureFlags/useFeatureFlag";
-
-interface ProjectSummaryFormProps {
- project: IProject;
- initialValues: Partial;
- onSubmit: any;
- handleSaveData: (message: string) => Promise;
- removeDocument: (event, documentGuid: string) => Promise;
- archivedDocuments: MajorMineApplicationDocument[];
- onArchivedDocuments: (mineGuid, projectSummaryGuid) => Promise;
- isNewProject: boolean;
-}
-
-const unassignedProjectLeadEntry = {
- label: "Unassigned",
- value: null,
-};
-
-const contactFields = ({ fields, isNewProject, isEditMode }) => {
- return (
- <>
- {fields.map((field, index) => {
- return (
-
- {index === 0 ? (
-
Primary project contact
- ) : (
- <>
-
-
- Additional project contact #{index}
-
-
- {!isNewProject && (
- fields.remove(index)}
- okText="Remove"
- cancelText="Cancel"
- >
-
-
-
-
- )}
-
-
- >
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {index === 0 &&
Additional project contacts (optional)
}
-
- );
- })}
- {(isNewProject || isEditMode) && (
- {
- fields.push({ is_primary: false });
- }}
- title="Add additional project contacts"
- >
- Add additional project contacts
-
- )}
- >
- );
-};
-
-const ProjectSummaryForm: FC & ProjectSummaryFormProps> = (
- props
-) => {
- const { isFeatureEnabled } = useFeatureFlag();
- const majorProjectsFeatureEnabled = isFeatureEnabled(Feature.MAJOR_PROJECT_LINK_PROJECTS);
- const [isEditMode, setIsEditMode] = useState(false);
- const projectLeads: IGroupedDropdownList = useSelector(getDropdownProjectLeads);
- const userRoles: string[] = useSelector(getUserAccessData);
- const formSelector = formValueSelector(FORM.ADD_EDIT_PROJECT_SUMMARY);
- const expected_draft_irt_submission_date = useSelector((state) =>
- formSelector(state, "expected_draft_irt_submission_date")
- );
- const expected_permit_application_date = useSelector((state) =>
- formSelector(state, "expected_permit_application_date")
- );
- const expected_permit_receipt_date = useSelector((state) =>
- formSelector(state, "expected_permit_receipt_date")
- );
- const documents = useSelector((state) => formSelector(state, "documents"));
-
- const projectSummaryStatusCodes = useSelector(getDropdownProjectSummaryStatusCodes);
- const projectSummaryDocumentTypesHash = useSelector(getProjectSummaryDocumentTypesHash);
-
- const projectLeadData = [unassignedProjectLeadEntry, ...projectLeads[0]?.opt];
- const { mineGuid } = useParams<{ mineGuid: string }>();
- const history = useHistory();
-
- const renderProjectDetails = () => {
- const {
- project: { project_lead_party_guid },
- } = props;
- return (
-
- {!props.isNewProject && !project_lead_party_guid && (
-
- Please assign a Project Lead to this project via the{" "}
- Project contacts section.
-
- }
- type="warning"
- showIcon
- />
- )}
-
-
-
- Project details
-
-
- {props.initialValues?.status_code && (
-
-
- value !== "DFT")}
- disabled={!isEditMode}
- />
-
-
- )}
-
-
-
-
- Proponent project tracking ID (optional)
-
-
- If your company uses a tracking number to identify projects, please provide it
- here.
-
- >
- }
- component={renderConfig.FIELD}
- validate={[maxLength(20)]}
- disabled={!props.isNewProject && !isEditMode}
- />
-
- Project Overview
-
-
- Provide a 2-3 paragraph high-level description of your proposed project.
-
- >
- }
- component={renderConfig.AUTO_SIZE_FIELD}
- minRows={10}
- validate={[maxLength(4000), required]}
- disabled={!props.isNewProject && !isEditMode}
- />
-
- {majorProjectsFeatureEnabled && (
-
-
- routes.PRE_APPLICATIONS.dynamicRoute(p.project_guid, p.project_summary_guid)
- }
- />
-
- )}
-
-
- );
- };
-
- const renderContacts = () => {
- return (
-
- Project contacts
-
EMLI contacts
-
-
- Project Lead}
- component={renderConfig.SELECT}
- data={projectLeadData}
- disabled={!props.isNewProject && !isEditMode}
- />
-
-
- Proponent contacts
- <>
-
- >
-
- );
- };
-
- const renderProjectDates = () => {
- return (
-
-
Project dates
-
-
-
-
-
-
-
-
-
-
-
- );
- };
-
- const renderDocuments = () => {
- const canRemoveDocuments =
- userRoles.includes(USER_ROLES.role_admin) ||
- userRoles.includes(USER_ROLES.role_edit_project_summaries);
- return (
-
- );
- };
-
- const renderArchivedDocuments = () => {
- return ;
- };
-
- const cancelEdit = () => {
- props.reset();
- setIsEditMode(false);
- };
-
- const toggleEditMode = () => {
- setIsEditMode(!isEditMode);
- };
-
- return (
-
- );
-};
-
-const mapDispatchToProps = {
- change,
-};
-
-export default (compose(
- withRouter,
- connect(null, mapDispatchToProps),
- reduxForm({
- form: FORM.ADD_EDIT_PROJECT_SUMMARY,
- enableReinitialize: true,
- touchOnBlur: true,
- touchOnChange: false,
- })
-)(ProjectSummaryForm) as any) as FC;
diff --git a/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.js b/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.js
index 6d80d61a2b..b7e2914e5b 100644
--- a/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.js
+++ b/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.js
@@ -86,7 +86,7 @@ export const MajorProjectTable = (props) => {
sortField: "project_title",
sorter: true,
render: (text, record) => (
-
+
{text}
),
@@ -130,7 +130,7 @@ export const MajorProjectTable = (props) => {
-
+
Open
diff --git a/services/core-web/src/components/mine/Projects/InformationRequirementsTableTab.js b/services/core-web/src/components/mine/Projects/InformationRequirementsTableTab.js
index d4ebddf927..fd3fd41413 100644
--- a/services/core-web/src/components/mine/Projects/InformationRequirementsTableTab.js
+++ b/services/core-web/src/components/mine/Projects/InformationRequirementsTableTab.js
@@ -195,7 +195,7 @@ export class InformationRequirementsTableTab extends Component {
-
+
Back to: {mine_name} Project overview
diff --git a/services/core-web/src/components/mine/Projects/MineProjectTable.js b/services/core-web/src/components/mine/Projects/MineProjectTable.js
index acba62ba23..9ba845221b 100644
--- a/services/core-web/src/components/mine/Projects/MineProjectTable.js
+++ b/services/core-web/src/components/mine/Projects/MineProjectTable.js
@@ -91,7 +91,7 @@ export const MineProjectTable = (props) => {
-
+
Open
diff --git a/services/core-web/src/components/mine/Projects/Project.js b/services/core-web/src/components/mine/Projects/Project.js
index 414f7cf411..fefdfa5c70 100644
--- a/services/core-web/src/components/mine/Projects/Project.js
+++ b/services/core-web/src/components/mine/Projects/Project.js
@@ -89,10 +89,10 @@ export class Project extends Component {
project_guid,
information_requirements_table: { irt_guid },
} = this.props.project;
- let url = routes.PROJECTS.dynamicRoute(project_guid);
+ let url = routes.EDIT_PROJECT.dynamicRoute(project_guid);
switch (activeTab) {
case "overview":
- url = routes.PROJECTS.dynamicRoute(project_guid);
+ url = routes.EDIT_PROJECT.dynamicRoute(project_guid);
break;
case "intro-project-overview":
url = routes.INFORMATION_REQUIREMENTS_TABLE.dynamicRoute(project_guid, irt_guid);
@@ -107,7 +107,7 @@ export class Project extends Component {
url = routes.PROJECT_DECISION_PACKAGE.dynamicRoute(project_guid);
break;
default:
- url = routes.PROJECTS.dynamicRoute(project_guid);
+ url = routes.EDIT_PROJECT.dynamicRoute(project_guid);
}
return this.props.history.replace(url);
};
diff --git a/services/core-web/src/components/mine/Projects/ProjectOverviewTab.js b/services/core-web/src/components/mine/Projects/ProjectOverviewTab.js
index f60d31eb38..e06ec554ce 100644
--- a/services/core-web/src/components/mine/Projects/ProjectOverviewTab.js
+++ b/services/core-web/src/components/mine/Projects/ProjectOverviewTab.js
@@ -18,7 +18,7 @@ import CustomPropTypes from "@/customPropTypes";
import ProjectStagesTable from "./ProjectStagesTable";
import withFeatureFlag from "@mds/common/providers/featureFlags/withFeatureFlag";
import { Feature } from "@mds/common";
-import ProjectLinks from "@mds/common/components/projects/ProjectLinks";
+import ProjectLinks from "@mds/common/components/projectSummary/ProjectLinks";
const propTypes = {
informationRequirementsTableStatusCodesHash: PropTypes.objectOf(PropTypes.string).isRequired,
@@ -138,7 +138,7 @@ export class ProjectOverviewTab extends Component {
link: (
View
@@ -254,7 +254,7 @@ export class ProjectOverviewTab extends Component {
- routes.PRE_APPLICATIONS.dynamicRoute(p.project_guid, p.project_summary_guid)
+ routes.EDIT_PROJECT_SUMMARY.dynamicRoute(p.project_guid, p.project_summary_guid)
}
/>
)}
diff --git a/services/core-web/src/components/mine/Projects/ProjectSummary.tsx b/services/core-web/src/components/mine/Projects/ProjectSummary.tsx
index 26201bfe9b..9dc4e024d6 100644
--- a/services/core-web/src/components/mine/Projects/ProjectSummary.tsx
+++ b/services/core-web/src/components/mine/Projects/ProjectSummary.tsx
@@ -1,9 +1,9 @@
import React, { FC, useEffect, useState } from "react";
import { withRouter, Link, Prompt, useParams, useHistory, useLocation } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
-import { submit, getFormValues, getFormSyncErrors, reset, touch } from "redux-form";
+import { reset } from "redux-form";
import * as routes from "@/constants/routes";
-import { Tabs, Tag } from "antd";
+import { Button, Col, Row, Tag } from "antd";
import EnvironmentOutlined from "@ant-design/icons/EnvironmentOutlined";
import ArrowLeftOutlined from "@ant-design/icons/ArrowLeftOutlined";
@@ -13,108 +13,88 @@ import {
getProject,
} from "@mds/common/redux/selectors/projectSelectors";
import { FORM, Feature } from "@mds/common";
-import { getMineDocuments } from "@mds/common/redux/reducers/mineReducer";
-import { getProjectSummaryAuthorizationTypesArray } from "@mds/common/redux/selectors/staticContentSelectors";
+import { getMineById } from "@mds/common/redux/reducers/mineReducer";
import withFeatureFlag from "@mds/common/providers/featureFlags/withFeatureFlag";
import {
createProjectSummary,
fetchProjectById,
- fetchProjectSummaryById,
- removeDocumentFromProjectSummary,
updateProject,
updateProjectSummary,
} from "@mds/common/redux/actionCreators/projectActionCreator";
-import {
- fetchMineDocuments,
- fetchMineRecordById,
-} from "@mds/common/redux/actionCreators/mineActionCreator";
-import { flattenObject } from "@mds/common/redux/utils/helpers";
-import { clearProjectSummary } from "@mds/common/redux/actions/projectActions";
+import { fetchMineRecordById } from "@mds/common/redux/actionCreators/mineActionCreator";
import Loading from "@/components/common/Loading";
-import ScrollSideMenu from "@mds/common/components/common/ScrollSideMenu";
import { useFeatureFlag } from "@mds/common/providers/featureFlags/useFeatureFlag";
import LoadingWrapper from "@/components/common/wrappers/LoadingWrapper";
-import ProjectSummaryForm from "@/components/Forms/projectSummaries/ProjectSummaryForm";
-import { isArray } from "lodash";
+import ProjectSummaryForm, {
+ getProjectFormTabs,
+} from "@mds/common/components/projectSummary/ProjectSummaryForm";
+import { fetchRegions } from "@mds/common/redux/slices/regionsSlice";
+import { clearProjectSummary } from "@mds/common/redux/actions/projectActions";
export const ProjectSummary: FC = () => {
const dispatch = useDispatch();
-
const history = useHistory();
const { pathname } = useLocation();
- const { mineGuid, projectSummaryGuid, projectGuid, tab } = useParams<{
+ const { mineGuid, projectSummaryGuid, projectGuid, tab, mode } = useParams<{
mineGuid: string;
projectSummaryGuid: string;
projectGuid: string;
tab: string;
+ mode: string;
}>();
+ const mine = useSelector((state) => getMineById(state, mineGuid));
const formattedProjectSummary = useSelector(getFormattedProjectSummary);
const project = useSelector(getProject);
- const formValues = useSelector(getFormValues(FORM.ADD_EDIT_PROJECT_SUMMARY));
- const formErrors = useSelector(getFormSyncErrors(FORM.ADD_EDIT_PROJECT_SUMMARY));
const anyTouched = useSelector(
(state) => state.form[FORM.ADD_EDIT_PROJECT_SUMMARY]?.anyTouched || false
);
- const mineDocuments = useSelector(getMineDocuments);
- const projectSummaryAuthorizationTypesArray = useSelector(
- getProjectSummaryAuthorizationTypesArray
- );
const { isFeatureEnabled } = useFeatureFlag();
+ const amsFeatureEnabled = isFeatureEnabled(Feature.AMS_AGENT);
+ const projectFormTabs = getProjectFormTabs(amsFeatureEnabled, true);
- const [isValid, setIsValid] = useState(true);
- const [isLoaded, setIsLoaded] = useState(false);
- const [isNewProject, setIsNewProject] = useState(false);
- const [fixedTop, setFixedTop] = useState(false);
- const [activeTab, setActiveTab] = useState("project-descriptions");
- const [uploadedDocuments, setUploadedDocuments] = useState([]);
- const [mineName, setMineName] = useState(formattedProjectSummary.mine_name);
+ const isExistingProject = Boolean(projectGuid && projectSummaryGuid);
+ const isDefaultLoaded = isExistingProject
+ ? formattedProjectSummary?.project_summary_guid === projectSummaryGuid &&
+ formattedProjectSummary?.project_guid === projectGuid
+ : mine?.mine_guid === mineGuid;
+ const isDefaultEditMode = !isExistingProject || mode === "edit";
- const [projectMineGuid, setProjectMineGuid] = useState(
- formattedProjectSummary.mine_guid ?? mineGuid
- );
-
- const handleScroll = () => {
- if (window.pageYOffset > 170 && !fixedTop) {
- setFixedTop(true);
- } else if (window.pageYOffset <= 170 && fixedTop) {
- setFixedTop(false);
- }
- };
+ const [isLoaded, setIsLoaded] = useState(isDefaultLoaded);
+ // isNewProject on CORE and isEditMode on MS are inverses of each other
+ const [isNewProject, setIsNewProject] = useState(isDefaultEditMode);
+ // this isEditMode doesn't mean new/edit, it's edit/view
+ const [isEditMode, setIsEditMode] = useState(isDefaultEditMode);
+ const activeTab = tab ?? projectFormTabs[0];
+ const mineName = mine?.mine_name ?? formattedProjectSummary?.mine_name ?? "";
const handleFetchData = () => {
setIsLoaded(false);
if (projectGuid && projectSummaryGuid) {
- return dispatch(fetchProjectById(projectGuid))
- .then((res) => {
- setIsLoaded(true);
- setProjectMineGuid(res.mine_guid);
- setIsValid(true);
- setIsNewProject(false);
- setUploadedDocuments(res.project_summary.documents);
- dispatch(
- // @ts-ignore (argument mismatch, needs to be made optional)
- fetchMineDocuments(res.mine_guid, {
- project_summary_guid: projectSummaryGuid,
- is_archived: true,
- })
- );
- })
- .catch((err) => {
- console.error(err);
- setIsLoaded(false);
- setIsValid(false);
- });
+ setIsNewProject(false);
+ dispatch(fetchRegions(undefined));
+ dispatch(fetchProjectById(projectGuid));
+ } else {
+ dispatch(fetchMineRecordById(mineGuid));
}
- return dispatch(fetchMineRecordById(projectMineGuid)).then((res) => {
- setIsNewProject(true);
- setIsLoaded(true);
- setActiveTab(tab);
- setMineName(res.data.mine_name);
- });
};
+ useEffect(() => {
+ if (!isLoaded) {
+ handleFetchData();
+ }
+ return () => {
+ dispatch(clearProjectSummary());
+ };
+ }, []);
+
+ useEffect(() => {
+ if ((formattedProjectSummary?.project_guid && !isNewProject) || mine?.mine_guid) {
+ setIsLoaded(true);
+ }
+ }, [formattedProjectSummary, mine]);
+
const removeUploadedDocument = (payload, docs) => {
if (Array.isArray(payload.documents)) {
const uploadedGUIDs = new Set(docs.map((doc) => doc.document_manager_guid));
@@ -125,202 +105,103 @@ export const ProjectSummary: FC = () => {
return payload;
};
- const transformAuthorizations = (valuesFromForm: any) => {
- const { authorizations = {}, project_summary_guid } = valuesFromForm;
-
- const transformAuthorization = (type, authorization) => {
- return { ...authorization, project_summary_authorization_type: type, project_summary_guid };
- };
-
- let updatedAuthorizations = [];
- let newAmsAuthorizations = [];
- let amendAmsAuthorizations = [];
-
- projectSummaryAuthorizationTypesArray.forEach((type) => {
- const authsOfType = authorizations[type];
- if (authsOfType) {
- if (isArray(authsOfType)) {
- const formattedAuthorizations = authsOfType.map((a) => {
- return transformAuthorization(type, a);
- });
- updatedAuthorizations = updatedAuthorizations.concat(formattedAuthorizations);
- } else {
- newAmsAuthorizations = newAmsAuthorizations.concat(
- authsOfType?.NEW.map((a) =>
- transformAuthorization(type, { ...a, project_summary_permit_type: ["NEW"] })
- )
- );
- amendAmsAuthorizations = amendAmsAuthorizations.concat(
- authsOfType?.AMENDMENT.map((a) =>
- transformAuthorization(type, { ...a, project_summary_permit_type: ["AMENDMENT"] })
- )
- );
- }
- }
+ const handleCreateProjectSummary = async (values, message) => {
+ return dispatch(
+ createProjectSummary(
+ {
+ mineGuid,
+ },
+ values,
+ message
+ )
+ ).then(({ data: { project_guid, project_summary_guid } }) => {
+ history.replace(
+ routes.EDIT_PROJECT_SUMMARY.dynamicRoute(
+ project_guid,
+ project_summary_guid,
+ projectFormTabs[1],
+ false
+ )
+ );
});
- return {
- authorizations: updatedAuthorizations,
- ams_authorizations: { amendments: amendAmsAuthorizations, new: newAmsAuthorizations },
- };
};
- const handleTransformPayload = (payload) => {
- const values = removeUploadedDocument(payload, uploadedDocuments);
- let payloadValues: any = {};
- const updatedAuthorizations = transformAuthorizations(values);
- payloadValues = {
- ...values,
- ...updatedAuthorizations,
- };
-
- delete payloadValues.authorizationTypes;
- return payloadValues;
- };
-
- const handleUpdate = (message) => {
- const { project_guid, mrc_review_required, contacts, project_lead_party_guid } = formValues;
+ const handleUpdateProjectSummary = async (payload, message) => {
+ setIsLoaded(false);
return dispatch(
updateProjectSummary(
- { projectGuid: project_guid, projectSummaryGuid },
- handleTransformPayload({ ...formValues }),
+ {
+ projectGuid,
+ projectSummaryGuid,
+ },
+ payload,
message
)
)
- .then(() => {
- dispatch(
+ .then(async () => {
+ await dispatch(
updateProject(
- { projectGuid: project_guid },
- { mrc_review_required, contacts, project_lead_party_guid },
+ { projectGuid },
+ { mrc_review_required: payload.mrc_review_required, contacts: payload.contacts },
"Successfully updated project.",
false
)
);
})
- .then(() => {
+ .then(async () => {
handleFetchData();
});
};
- const handleCreate = (message) => {
- dispatch(
- createProjectSummary(
- { mineGuid: projectMineGuid },
- handleTransformPayload({ status_code: "SUB", ...formValues }),
- message
- )
- )
- .then(({ data: { project_guid, project_summary_guid } }) => {
- history.push(routes.PRE_APPLICATIONS.dynamicRoute(project_guid, project_summary_guid));
- })
- .then(() => handleFetchData());
- };
-
- const reloadData = async (mine_guid, project_summary_guid) => {
- setIsLoaded(false);
- try {
- await dispatch(fetchProjectSummaryById(mine_guid, project_summary_guid));
- } finally {
- setIsLoaded(true);
+ const handleTabChange = (newTab: string) => {
+ if (!newTab) {
+ return;
}
+ const url = !isNewProject
+ ? routes.EDIT_PROJECT_SUMMARY.dynamicRoute(
+ projectGuid,
+ projectSummaryGuid,
+ newTab,
+ !isEditMode
+ )
+ : routes.ADD_PROJECT_SUMMARY.dynamicRoute(mineGuid, newTab);
+ history.push(url);
};
- const handleRemoveDocument = (event, documentGuid) => {
- event.preventDefault();
- const { project_summary_guid, project_guid, mine_guid } = formattedProjectSummary;
- return dispatch(
- removeDocumentFromProjectSummary(project_guid, project_summary_guid, documentGuid)
- )
- .then(() => {
- reloadData(mine_guid, project_summary_guid);
- })
- .finally(() => setIsLoaded(true));
- };
+ const handleSaveData = async (formValues, newActiveTab?: string) => {
+ const message = newActiveTab
+ ? "Successfully updated the project description."
+ : "Successfully submitted a project description to the Province of British Columbia.";
- const handleSaveData = (message) => {
- dispatch(submit(FORM.ADD_EDIT_PROJECT_SUMMARY));
- dispatch(touch(FORM.ADD_EDIT_PROJECT_SUMMARY));
- const errors = Object.keys(flattenObject(formErrors));
- if (errors.length === 0) {
+ let status_code = formattedProjectSummary.status_code;
+ if (!status_code || isNewProject) {
+ status_code = "DFT";
+ } else if (!newActiveTab) {
+ status_code = "SUB";
+ }
+ const values = { ...formValues, status_code: status_code };
+
+ try {
if (isNewProject) {
- return handleCreate(message);
+ await handleCreateProjectSummary(values, message);
+ }
+ if (projectGuid && projectSummaryGuid) {
+ await handleUpdateProjectSummary(values, message);
+ handleTabChange(newActiveTab);
}
- return handleUpdate(message);
+ } catch (err) {
+ console.log(err);
+ setIsLoaded(true);
}
- return null;
};
- useEffect(() => {
- handleFetchData();
- window.addEventListener("scroll", handleScroll);
-
- return () => {
- window.removeEventListener("scroll", handleScroll);
- dispatch(clearProjectSummary());
- };
- }, [projectSummaryGuid]);
-
- if (!isValid) {
- return ;
- }
if (!isLoaded) {
return ;
}
- const tabItems = [
- {
- key: "project-descriptions",
- label: "Project Descriptions",
- children: (
-
-
-
{}}
- project={project}
- isNewProject={isNewProject}
- handleSaveData={handleSaveData}
- removeDocument={handleRemoveDocument}
- onArchivedDocuments={reloadData.bind(this, projectMineGuid, projectSummaryGuid)}
- archivedDocuments={mineDocuments}
- initialValues={
- !isNewProject
- ? { ...formattedProjectSummary, mrc_review_required: project.mrc_review_required }
- : {
- contacts: [
- {
- is_primary: true,
- name: null,
- job_title: null,
- company_name: null,
- email: null,
- phone_number: null,
- phone_extension: null,
- },
- ],
- documents: [],
- }
- }
- />
-
-
- ),
- },
- ];
-
- const menuOptions = [
- { href: "project-details", title: "Project details" },
- { href: "authorizations-involved", title: "Authorizations Involved" },
- { href: "project-dates", title: "Project dates" },
- { href: "project-contacts", title: "Project contacts" },
- { href: "document-details", title: "Documents" },
- isFeatureEnabled(Feature.MAJOR_PROJECT_ARCHIVE_FILE) && {
- href: "archived-documents",
- title: "Archived Documents",
- },
- ].filter(Boolean);
+ const initialValues = isNewProject
+ ? {}
+ : { ...formattedProjectSummary, mrc_review_required: project.mrc_review_required };
return (
<>
@@ -338,53 +219,57 @@ export const ProjectSummary: FC = () => {
}}
/>
-
-
- {!isNewProject
- ? formattedProjectSummary.project_summary_title
- : "New Project Description"}
-
-
-
-
- {mineName}
-
-
-
-
-
-
- Back to: {mineName} Project overview
-
-
-
-
+
+
+ {!isNewProject
+ ? formattedProjectSummary.project_summary_title
+ : "New Project Description"}
+
+
+
+
+ {mineName}
+
+
+
+
+
+
+ Back to: {mineName} Project overview
+
+
+
+
setIsEditMode(!isEditMode)}>
+ {isEditMode ? "Cancel" : "Edit Project Description"}
+
+
+
+
-
>
);
diff --git a/services/core-web/src/components/navigation/NotificationDrawer.tsx b/services/core-web/src/components/navigation/NotificationDrawer.tsx
index 1e28e312ed..be3a891bf1 100644
--- a/services/core-web/src/components/navigation/NotificationDrawer.tsx
+++ b/services/core-web/src/components/navigation/NotificationDrawer.tsx
@@ -16,8 +16,8 @@ import {
INFORMATION_REQUIREMENTS_TABLE,
MINE_TAILINGS_DETAILS,
NOTICE_OF_DEPARTURE,
- PRE_APPLICATIONS,
- PROJECTS,
+ EDIT_PROJECT_SUMMARY,
+ EDIT_PROJECT,
VIEW_MINE_INCIDENT,
PROJECT_DOCUMENT_MANAGEMENT,
REPORT_VIEW_EDIT,
@@ -116,7 +116,7 @@ const NotificationDrawer: FC
= (props) => {
notification.notification_document.metadata.entity_guid
);
case "ProjectSummary":
- return PRE_APPLICATIONS.dynamicRoute(
+ return EDIT_PROJECT_SUMMARY.dynamicRoute(
notification.notification_document.metadata.project.project_guid,
notification.notification_document.metadata.entity_guid
);
@@ -126,7 +126,7 @@ const NotificationDrawer: FC = (props) => {
notification.notification_document.metadata.entity_guid
);
case "MajorMineApplication":
- return PROJECTS.dynamicRoute(
+ return EDIT_PROJECT.dynamicRoute(
notification.notification_document.metadata.project.project_guid,
"final-app"
);
diff --git a/services/core-web/src/components/parties/PartyProfile.js b/services/core-web/src/components/parties/PartyProfile.js
index 0121af5c54..6cf1acd687 100644
--- a/services/core-web/src/components/parties/PartyProfile.js
+++ b/services/core-web/src/components/parties/PartyProfile.js
@@ -32,6 +32,7 @@ import {
getPartyRelationshipTypeHash,
getPartyBusinessRoleOptionsHash,
} from "@mds/common/redux/selectors/staticContentSelectors";
+import { deletePartyWalletConnection } from "@mds/common/redux/actionCreators/verifiableCredentialActionCreator";
import { formatDate, dateSorter } from "@common/utils/helpers";
import * as Strings from "@mds/common/constants/strings";
import { EDIT } from "@/constants/assets";
@@ -56,6 +57,7 @@ const propTypes = {
history: PropTypes.shape({ push: PropTypes.func }).isRequired,
updateParty: PropTypes.func.isRequired,
deleteParty: PropTypes.func.isRequired,
+ deletePartyWalletConnection: PropTypes.func.isRequired,
openModal: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
parties: PropTypes.arrayOf(CustomPropTypes.party).isRequired,
@@ -130,6 +132,15 @@ export class PartyProfile extends Component {
.finally(() => this.setState({ deletingParty: false }));
};
+ deletePartyWalletConnection = () => {
+ const { id } = this.props.match.params;
+ this.setState({ deletingParty: true });
+ this.props
+ .deletePartyWalletConnection(id)
+ .then(this.props.fetchPartyById(id))
+ .finally(() => this.setState({ deletingParty: false }));
+ };
+
render() {
const { id } = this.props.match.params;
const party = this.props.parties[id];
@@ -347,13 +358,35 @@ export class PartyProfile extends Component {
)}
- {isFeatureEnabled(Feature.VERIFIABLE_CREDENTIALS) &&
- party.party_type_code === "ORG" && (
+ {isFeatureEnabled(Feature.VERIFIABLE_CREDENTIALS) && party.party_type_code === "ORG" && (
+ <>
Digital Wallet Connection Status:{" "}
{VC_CONNECTION_STATES[party?.digital_wallet_connection_status]}
+ {VC_CONNECTION_STATES[party?.digital_wallet_connection_status] === "Active" && (
+
+
+ Are you sure you want to delete the digitial wallet connection for
+ '
+ {party.name}'? This is irreversable and destructive.
+
+
+ }
+ onConfirm={this.deletePartyWalletConnection}
+ okText="Yes"
+ cancelText="No"
+ >
+
+
+ Delete Wallet Connection
+
+
+ )}
- )}
+ >
+ )}
fetchPartyById,
fetchMineBasicInfoList,
deleteParty,
+ deletePartyWalletConnection,
updateParty,
openModal,
closeModal,
diff --git a/services/core-web/src/constants/routes.ts b/services/core-web/src/constants/routes.ts
index 85816fa811..eb20817267 100644
--- a/services/core-web/src/constants/routes.ts
+++ b/services/core-web/src/constants/routes.ts
@@ -195,21 +195,27 @@ export const MINE_PRE_APPLICATIONS = {
};
export const ADD_PROJECT_SUMMARY = {
- route: "/mines/:mineGuid/project-description/new",
- dynamicRoute: (mineGuid) => `/mines/${mineGuid}/project-description/new`,
+ route: "/mines/:mineGuid/project-description/new/:tab",
+ dynamicRoute: (mineGuid, tab = "basic-information") =>
+ `/mines/${mineGuid}/project-description/new/${tab}`,
component: ProjectSummary,
};
-export const PRE_APPLICATIONS = {
- route: "/pre-applications/:projectGuid/project-description/:projectSummaryGuid",
- dynamicRoute: (projectGuid, projectSummaryGuid) =>
- `/pre-applications/${projectGuid}/project-description/${projectSummaryGuid}`,
- hashRoute: (projectGuid, projectSummaryGuid, link) =>
- `/pre-applications/${projectGuid}/project-description/${projectSummaryGuid}/${link}`,
+export const EDIT_PROJECT_SUMMARY = {
+ route: "/pre-applications/:projectGuid/project-description/:projectSummaryGuid/:mode/:tab",
+ dynamicRoute: (
+ projectGuid,
+ projectSummaryGuid,
+ activeTab = "basic-information",
+ viewMode = true
+ ) =>
+ `/pre-applications/${projectGuid}/project-description/${projectSummaryGuid}/${
+ viewMode ? "view" : "edit"
+ }/${activeTab}`,
component: ProjectSummary,
};
-export const PROJECTS = {
+export const EDIT_PROJECT = {
route: "/pre-applications/:projectGuid/:tab",
dynamicRoute: (projectGuid, tab = "overview") => `/pre-applications/${projectGuid}/${tab}`,
component: Project,
diff --git a/services/core-web/src/routes/MineDashboardRoutes.js b/services/core-web/src/routes/MineDashboardRoutes.js
index ed4e880052..ddd5035c21 100644
--- a/services/core-web/src/routes/MineDashboardRoutes.js
+++ b/services/core-web/src/routes/MineDashboardRoutes.js
@@ -27,15 +27,15 @@ const MineDashboardRoutes = () => (
/>
-
+
({
}),
}));
+window.scrollTo = jest.fn();
const location = JSON.stringify(window.location);
delete window.location;
diff --git a/services/core-web/src/styles/components/SteppedForm.scss b/services/core-web/src/styles/components/SteppedForm.scss
index 34d7506fc8..a0fd938756 100644
--- a/services/core-web/src/styles/components/SteppedForm.scss
+++ b/services/core-web/src/styles/components/SteppedForm.scss
@@ -15,7 +15,7 @@
}
.stepped-form>.ant-menu-item-selected {
- border-left: 4px solid $gov-blue;
+ border-left: 4px solid $violet;
background-color: #FFFFFF !important;
border-right: unset !important;
}
diff --git a/services/core-web/src/tests/components/Forms/projectSummaries/ProjectSummaryForm.spec.tsx b/services/core-web/src/tests/components/Forms/projectSummaries/ProjectSummaryForm.spec.tsx
deleted file mode 100644
index 59414c6448..0000000000
--- a/services/core-web/src/tests/components/Forms/projectSummaries/ProjectSummaryForm.spec.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from "react";
-import { render } from "@testing-library/react";
-import ProjectSummaryForm from "@/components/Forms/projectSummaries/ProjectSummaryForm";
-import * as MOCK from "@/tests/mocks/dataMocks";
-import { store } from "@/App";
-import { Provider } from "react-redux";
-import { BrowserRouter } from "react-router-dom";
-
-const props: any = {};
-
-const setupProps = () => {
- props.handleSubmit = jest.fn();
- props.submitting = false;
- props.formValues = { contacts: [{}] };
- props.initialValues = {
- projectSummaryAuthorizationTypes: [],
- authorizations: [],
- };
- props.projectSummary = { documents: [], contacts: [{}] };
- props.projectSummaryDocumentTypesHash = {};
- props.projectSummaryPermitTypesHash = {};
- props.projectSummaryAuthorizationTypesHash = {};
- props.project = MOCK.PROJECT;
- props.projectLeads = [
- { groupName: "Active", opt: [] },
- { groupName: "Inactive", opt: [] },
- ];
- props.projectSummaryStatusCodes = [];
- props.userRoles = [];
-};
-
-beforeEach(() => {
- setupProps();
-});
-
-describe("ProjectSummaryForm", () => {
- it("renders properly", () => {
- const { container } = render(
-
-
-
-
-
- );
- expect(container.firstChild).toMatchSnapshot();
- });
-});
diff --git a/services/core-web/src/tests/components/Forms/projectSummaries/__snapshots__/ProjectSummaryForm.spec.tsx.snap b/services/core-web/src/tests/components/Forms/projectSummaries/__snapshots__/ProjectSummaryForm.spec.tsx.snap
deleted file mode 100644
index 0ea22be7d6..0000000000
--- a/services/core-web/src/tests/components/Forms/projectSummaries/__snapshots__/ProjectSummaryForm.spec.tsx.snap
+++ /dev/null
@@ -1,1509 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ProjectSummaryForm renders properly 1`] = `
-
-`;
diff --git a/services/core-web/src/tests/components/Forms/reports/__snapshots__/RequestReportForm.spec.tsx.snap b/services/core-web/src/tests/components/Forms/reports/__snapshots__/RequestReportForm.spec.tsx.snap
index f853cad37e..989fff0ec6 100644
--- a/services/core-web/src/tests/components/Forms/reports/__snapshots__/RequestReportForm.spec.tsx.snap
+++ b/services/core-web/src/tests/components/Forms/reports/__snapshots__/RequestReportForm.spec.tsx.snap
@@ -61,6 +61,7 @@ exports[`RequestReportForm renders form properly 1`] = `