diff --git a/actions/index.js b/actions/index.js
index 1d933f06..1fb6b921 100644
--- a/actions/index.js
+++ b/actions/index.js
@@ -1896,22 +1896,30 @@ export const sendEmail = async (
// personalisation,
// reference
// );
- var queryUrl = "/api/email/notify";
+ // var queryUrl = "/api/email/notify";
- var config = {
- method: "post",
- url: queryUrl,
- data: mailData,
- };
+ // var config = {
+ // method: "post",
+ // url: queryUrl,
+ // data: mailData,
+ // };
- return axios(config)
- .then((res) => {
- //console.log("posted ", res.data);
- return res.data;
- })
- .catch((error) => {
- consoleLogger(error);
- });
+ // return axios(config)
+ // .then((res) => {
+ // //console.log("posted ", res.data);
+ // return res.data;
+ // })
+ // .catch((error) => {
+ // consoleLogger(error);
+ // });
+
+ try {
+ const response = await axios.post("/api/email/notify", mailData);
+ return response.data;
+ } catch (error) {
+ consoleLogger(error); // Optional custom logging
+ throw error; // ✅ Ensure the calling function sees the error
+ }
};
export const getNotice = () => {
@@ -2117,3 +2125,21 @@ export const getAddressSearch = async (searchString) => {
return ErrResponse;
});
};
+
+export const createCRMTask = async (formValues) => {
+ var queryUrl = "/api/endpoint/createcrmtask_api";
+
+ var data = formValues;
+ var config = {
+ method: "post",
+ url: queryUrl,
+ data: data,
+ };
+
+ try {
+ const res = await axios(config);
+ return res.data;
+ } catch (error) {
+ consoleLogger(error);
+ }
+};
diff --git a/components/header.js b/components/header.js
index f00769e1..cab4ce5b 100644
--- a/components/header.js
+++ b/components/header.js
@@ -60,6 +60,7 @@ const Header = (props) => {
"/myportal/searchresults",
"/myportal/representation",
"/myportal/viewall",
+ "/contactus",
];
const hasContactLink = [
@@ -280,7 +281,6 @@ const Header = (props) => {
}}
>
{" "}
- wwww
{t("common:available-in")}
diff --git a/components/homepage/casecomment.js b/components/homepage/casecomment.js
index 57235c3b..c84915a1 100644
--- a/components/homepage/casecomment.js
+++ b/components/homepage/casecomment.js
@@ -1,5 +1,6 @@
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
+import Link from "next/link";
export default function CaseComment() {
let { t } = useTranslation();
@@ -24,6 +25,15 @@ export default function CaseComment() {
{t("home:casescomment-card-paragraph-two")}
+
+
+
+ Contact us
+
+
);
diff --git a/components/myportal/searchcases.js b/components/myportal/searchcases.js
index f7167d30..da79bd11 100644
--- a/components/myportal/searchcases.js
+++ b/components/myportal/searchcases.js
@@ -171,6 +171,18 @@ let CaseSearch = (props) => {
>
{t("myportal:searchcases-card-advanced-link")}
+
+
+ Contact us
+
+
diff --git a/i18n.js b/i18n.js
index bfe06a35..725893d7 100644
--- a/i18n.js
+++ b/i18n.js
@@ -11,6 +11,7 @@ module.exports = {
"pages": {
"*": ["common", "search"],
"/": ["common", "home", "case"],
+ "/contactus": ["newappeal", "account"],
"/error": ["common", "home", "myportal"],
"/furtherdetails": ["common", "home", "myportal"],
"/myportal": ["myportal", "common", "home", "case"],
diff --git a/pages/api/endpoint/createcrmtask_api.js b/pages/api/endpoint/createcrmtask_api.js
new file mode 100644
index 00000000..ba098058
--- /dev/null
+++ b/pages/api/endpoint/createcrmtask_api.js
@@ -0,0 +1,188 @@
+/**
+ * @swagger
+ * /api/endpoint/createcase_api:
+ * post:
+ * tags: [Case]
+ * description: Create case
+ * summary: Phase 2
+ * parameters:
+ * - name: body
+ * in: requestbody
+ * required: true
+ * example: {}
+ * schema:
+ * type: object
+ * - name: contactid
+ * in: query
+ * required: true
+ * example: pinswg_planningappeals78s
+ * schema:
+ * type: string
+ * - name: appealTypeId
+ * in: query
+ * required: true
+ * example: pinswg_planningappeals78id
+ * schema:
+ * type: string
+ * - name: containername
+ * in: query
+ * required: true
+ * example: pinswg_planningappeals78id
+ * schema:
+ * type: string
+ * - name: lpaID
+ * in: query
+ * required: true
+ * example: pinswg_planningappeals78id
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Success
+ */
+
+import axios from "axios";
+import CryptoJS from "crypto-js";
+import _ from "lodash";
+import { getToken } from "../../../actions";
+import { getCaseBlob } from "../../../actions/azurestorage";
+const WORDKEY = process.env.HASHKEY;
+
+const WEBAPI_URL =
+ process.env.RELAY_ROOT ||
+ "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
+
+const hashAPIPath = (queryPath) => {
+ var hashlink = CryptoJS.HmacSHA256(
+ queryPath,
+ CryptoJS.enc.Hex.parse(WORDKEY)
+ );
+ hashlink = hashlink.toString(CryptoJS.enc.Hex);
+
+ //return "&hash=" + hashlink;
+
+ return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink;
+};
+
+export default async function ApiProxy(req, res) {
+ var createTaskBody = req.body;
+ var queryUrl = "tasks";
+ var token = await getToken();
+
+ let contactValue = createTaskBody.typeOfContact;
+
+ // const whichTeam = (contactValue) => {
+ // switch (contactValue) {
+ // case "plq":
+ // //Questions about Planning legislation or policy
+ // //PD - Via Email - Planning.Directorate@gov.wales
+
+ // return;
+ // break;
+ // case "ipq":
+ // //Questions or comments about applications for major infrastructure projects
+ // //Infrastructure
+ // return "7fb9e598-7d26-ec11-a97f-00224800e98c";
+ // break;
+ // case "pcpq":
+ // //Questions or comments about planning and environmental appeals
+ // //Casework
+ // return "8ffabad9-7c26-ec11-a97f-00224800e98c";
+ // break;
+ // case "pi":
+ // //Query about using the Casework Portal
+ // // Digital Support Team - PEDW
+ // return "5554882f-4606-ec11-a97f-00224800d374";
+ // break;
+ // case "appadvice":
+ // //Advice How to submit an appeal
+ // //R&D
+ // return "5554882f-4606-ec11-a97f-00224800d374";
+ // break;
+ // case "repadvice":
+ // //Advice How to comment on an appeal or application
+ // //R&D
+ // return "5554882f-4606-ec11-a97f-00224800d374";
+ // break;
+ // case "ceq":
+ // //Queries about forthcoming Hearing or Inquiry event
+ // //Inspector Scheduling
+ // return "4fddd7e8-7d26-ec11-a97f-00224800e98c";
+ // break;
+ // case "other":
+ // //Other enquiry for PEDW
+ // //Casework
+ // return "8ffabad9-7c26-ec11-a97f-00224800e98c";
+ // break;
+ // case "postdec":
+ // //Issues or questions about a case after the decision is issued
+ // //SLT Team
+ // return "cc3d987e-7c26-ec11-a97f-00224800e98c";
+ // break;
+ // case "comp":
+ // //General Complaints of Compliments
+ // //SLT Team
+ // return "cc3d987e-7c26-ec11-a97f-00224800e98c";
+
+ // break;
+ // default:
+ // // code block
+ // }
+ // };
+ // let teamId = whichTeam(contactValue);
+
+ const teamMap = {
+ ipq: "7fb9e598-7d26-ec11-a97f-00224800e98c", // Infrastructure
+ pcpq: "8ffabad9-7c26-ec11-a97f-00224800e98c", // Casework
+ pi: "5554882f-4606-ec11-a97f-00224800d374", // Digital Support Team
+ appadvice: "5554882f-4606-ec11-a97f-00224800d374",
+ repadvice: "5554882f-4606-ec11-a97f-00224800d374",
+ ceq: "4fddd7e8-7d26-ec11-a97f-00224800e98c", // Inspector Scheduling
+ other: "8ffabad9-7c26-ec11-a97f-00224800e98c",
+ postdec: "cc3d987e-7c26-ec11-a97f-00224800e98c", // SLT Team
+ comp: "cc3d987e-7c26-ec11-a97f-00224800e98c",
+ };
+
+ let teamId = teamMap[contactValue];
+
+ const payload = {
+ "subject": `Contact Us Enquiry - ${createTaskBody.contactSubject}`,
+ "description": `From: ${createTaskBody.contactEmail}\n\n${createTaskBody.contactbody}`,
+ "scheduledstart": new Date().toISOString(),
+ "scheduledend": new Date(
+ new Date().getTime() + 60 * 60000 * 24
+ ).toISOString(), // 30 mins
+ ...(teamId && { "ownerid@odata.bind": `/teams(${teamId})` }),
+ };
+
+ //console.log("/////Create Task:\n", payload, "\n//////////////");
+
+ var config = {
+ method: "post",
+ url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
+ headers: {
+ "OData-MaxVersion": "4.0",
+ "OData-Version": "4.0",
+ "Accept": "application/json",
+ "Prefer": 'odata.include-annotations="*",return=representation',
+ "Authorization": "Bearer " + token.access_token,
+ "Content-Type": "application/json",
+ },
+ data: JSON.stringify(payload),
+ };
+
+ //console.log("/////Create Task config:\n", config, "\n//////////////");
+
+ try {
+ const { data } = await axios(config);
+ return res.status(200).json(data);
+ } catch (error) {
+ console.error(
+ "CRM Task Creation Error:",
+ error.response?.data || error
+ );
+ return res
+ .status(400)
+ .json({ error: "Failed to create CRM task", details: error });
+ }
+}
diff --git a/pages/contactus.js b/pages/contactus.js
new file mode 100644
index 00000000..5e4ccc35
--- /dev/null
+++ b/pages/contactus.js
@@ -0,0 +1,515 @@
+import useTranslation from "next-translate/useTranslation";
+import Head from "next/head";
+import { useRouter } from "next/router";
+import Breadcrumbs from "../components/breadcrumbs";
+import CookieBanner from "../components/cookieBanner";
+import Footer from "../components/footer";
+import Header from "../components/header";
+import ServiceBanner from "../components/servicebanner";
+import Link from "next/link";
+import { PickList } from "../components/elements";
+import {
+ Field,
+ change,
+ formValueSelector,
+ reduxForm,
+ touch,
+ SubmissionError,
+} from "redux-form";
+import { connect, submitError } from "react-redux";
+import { createCRMTask, sendEmail } from "../actions";
+
+import { useState } from "react";
+
+const RenderPickList = ({
+ datafieldname,
+ picklistData,
+ id,
+ name,
+ label,
+ input,
+ errorMsg,
+ meta: { touched, error },
+}) => {
+ let { t } = useTranslation();
+
+ const required = (value) => {
+ return value || value == 0
+ ? undefined
+ : t("newappeal:is-required-label");
+ };
+
+ return (
+ <>
+
+
+ {label}
+
+ {touched && error && (
+
+ Error: {" "}
+ {error}
+
+ )}
+
+
+ {t("newappeal:select-default")}
+
+
+ Planning legislation query
+ Infrastructure procedure query
+
+ Planning casework procedure query
+
+ Portal Issues
+ Appeal submission advice
+
+ Representation submission advice
+
+ Casework Events Query
+ Any Other Enquiries
+ Post Decision
+ Complaints/Complements
+
+
+ >
+ );
+};
+
+const RenderMultiline = ({
+ id,
+ className,
+ rows,
+ cols,
+ datafieldname,
+ name,
+ label,
+ input,
+ errorMsg,
+ meta: { touched, error },
+ ...custom
+}) => {
+ return (
+ <>
+
+
+ {label}
+
+ {touched && error && (
+
+ Error: {" "}
+ {errorMsg || error}
+
+ )}
+
+
+ >
+ );
+};
+
+const RenderTextfield = ({
+ id,
+ className,
+ rows,
+ datafieldname,
+ name,
+ label,
+ input,
+ errorMsg,
+ meta: { touched, error },
+ ...custom
+}) => {
+ return (
+ <>
+
+
+ {label}
+
+ {touched && error && (
+
+ Error: {" "}
+ {errorMsg || error}
+
+ )}
+
+
+ >
+ );
+};
+
+const contactPurpose = (contactValue) => {
+ let contactPurposeBody;
+
+ switch (contactValue) {
+ case "plq":
+ contactPurposeBody = (
+
+ Questions about Planning legislation or policy
+
+ );
+ break;
+ case "ipq":
+ contactPurposeBody = (
+
+ Questions or comments about applications for major
+ infrastructure projects
+
+ );
+ break;
+ case "pcpq":
+ contactPurposeBody = (
+
+ Questions or comments about planning and environmental
+ appeals
+
+ );
+ break;
+ case "pi":
+ contactPurposeBody = (
+
+ Query about using the Casework Portal
+
+ );
+ break;
+ case "appadvice":
+ contactPurposeBody = (
+ Advice How to submit an appeal
+ );
+ break;
+ case "repadvice":
+ contactPurposeBody = (
+
+ Advice How to comment on an appeal or application
+
+ );
+ break;
+ case "ceq":
+ contactPurposeBody = (
+
+ Queries about forthcoming Hearing or Inquiry event
+
+ );
+ break;
+ case "other":
+ contactPurposeBody = (
+ Other enquiry for PEDW
+ );
+ break;
+ case "postdec":
+ contactPurposeBody = (
+
+ Issues or questions about a case after the decision is
+ issued
+
+ );
+ break;
+ case "comp":
+ contactPurposeBody = (
+ General Complaints of Compliments
+ );
+ break;
+ default:
+ // code block
+ }
+
+ return contactPurposeBody;
+};
+
+let t;
+
+let ContactUs = (props) => {
+ const { footerLinks, pages } = props;
+ let { t } = useTranslation();
+
+ const [formSubmitted, setFormSubmitted] = useState(false);
+
+ const router = useRouter();
+ const { locale } = router;
+
+ const required = (value) =>
+ value ? undefined : t("newappeal:is-required-label");
+
+ const emailRegex =
+ /^(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}|(?:\[(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]:.+)])$/;
+
+ const email = (value) =>
+ value && !emailRegex.test(value)
+ ? t("account:invalid-email-address-errormsg")
+ : undefined;
+
+ const onSubmit = async (formValues) => {
+ // Only called if validation passes
+ sendContactEmail(formValues);
+ createCRMTask(formValues);
+ console.log("Form submitted:", formValues);
+ };
+
+ const sendContactEmail = async (formValues) => {
+ Object.assign(formValues, {
+ "contactSubject": contactPurpose(formValues.typeOfContact).props
+ .children,
+ });
+
+ const reference = "PEDW-CONTACT-US";
+ const templateId = "69228c5f-8500-40ff-a6c5-95a9434c3ec8";
+ const templateIdCY = "b8f1f76d-4ab9-4a2f-b294-e3623c6095a3";
+
+ try {
+ const res = await sendEmail(
+ router.locale !== "en" ? templateIdCY : templateId,
+ formValues.contactEmail,
+ formValues,
+ reference
+ );
+
+ console.log("Email sent successfully.");
+ setFormSubmitted(true);
+ } catch (error) {
+ console.error("Email sending failed:", error);
+ alert("Something went wrong. Please try again later.");
+ // Required: inform redux-form this is a submit error
+ throw new SubmissionError({
+ _error: "Failed to send email. Please try again later.",
+ });
+ }
+ };
+
+ return (
+
+
+
{t("common:service-name")}
+
+
+
+
+
+
+
+
+
+
+
+ Contact us{" "}
+
+
+
+ {formSubmitted ? (
+
+
+ Thank you for your enquiry
+
+
+ We’ll get back to you as soon as
+ we can.
+
+
+ ) : (
+ <>
+
+ Lorem ipsum dolor sit amet,
+ consectetur adipiscing elit.
+ Nulla congue leo ac quam
+ pulvinar, id vulputate libero
+ volutpat. Maecenas ut odio id
+ nisl laoreet dictum. Nam
+ faucibus, leo vitae egestas
+ lacinia, enim tortor bibendum
+ lorem, ut tincidunt lacus sapien
+ quis lorem. Aenean interdum
+ dolor augue, ac cursus lectus
+ suscipit vitae.{" "}
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const selector = formValueSelector("contactUsForm"); // <-- same as form name
+
+// ContactUs = connect((state) => {
+// const typeOfContactValue = selector(state, "typeOfContact");
+
+// return {
+// typeOfContactValue,
+// initialValues: {
+// contactEmail:
+// state.accountDetails.accountDetails?.emailaddress1 || "",
+// },
+// };
+// })(ContactUs);
+
+const mapDispatchToProps = (dispatch) => ({});
+
+const mapStateToProps = (state) => {
+ const typeOfContactValue = selector(state, "typeOfContact");
+ const email = state.accountDetails.accountDetails?.emailaddress1;
+
+ return {
+ typeOfContactValue,
+ initialValues: email ? { contactEmail: email } : {},
+ };
+};
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(
+ reduxForm({
+ form: "contactUsForm",
+ enableReinitialize: true,
+ destroyOnUnmount: false,
+ touchOnBlur: true,
+ touchOnChange: true,
+ onSubmitFail: (errors, dispatch, subnittError, props) => {
+ if (errors) {
+ // Touch all fields with errors so validation messages appear
+ const errorFields = Object.keys(errors);
+ dispatch(touch("contactUsForm", ...errorFields));
+
+ // Scroll to first error
+ const firstErrorField = errorFields[0];
+ const errorElement = document.querySelector(
+ `[name="${firstErrorField}"]`
+ );
+ if (
+ errorElement &&
+ typeof errorElement.scrollIntoView === "function"
+ ) {
+ errorElement.scrollIntoView({
+ behavior: "smooth",
+ block: "center",
+ });
+ errorElement.focus({ preventScroll: true });
+ } else {
+ console.warn(
+ "Could not find element for error field:",
+ firstErrorField
+ );
+ }
+ }
+
+ console.log("Redux Form onSubmitFail:", submitError);
+ },
+ })(ContactUs)
+);