Merged PR 2070: media tab and player

Related work items: #21254, #21255, #21256
This commit is contained in:
Robert Bond
2026-02-04 11:23:21 +00:00
18 changed files with 1279 additions and 152 deletions
+103 -90
View File
@@ -34,7 +34,7 @@ export const getIP = (req) => {
console.info(
"\n====================\n Client IP address: " + ip,
"\n=====================\n"
"\n=====================\n",
);
};
@@ -42,7 +42,7 @@ export const consoleLogger = (err) => {
console.log(
"\n\n/////////////////////////////////////////////////\nRaw Error " +
err +
"\n\n/////////////////////////////////////////////////\n"
"\n\n/////////////////////////////////////////////////\n",
);
var errStr =
"\n\n/////////////////////////////////////////////////\nServer Error " +
@@ -102,7 +102,7 @@ export const getToken = () => {
.post(
`${accessTokenEndpoint}${tenantId}/oauth2/v2.0/token`,
tokenBody,
tokenConfig
tokenConfig,
)
.then((res) => res.data)
.then((data) => {
@@ -191,7 +191,7 @@ export const azureHeadersPagedCustom = (access_token, showNumberOfRecords) => {
export const hashAPIPath = (queryPath) => {
var hashlink = CryptoJS.HmacSHA256(
queryPath,
CryptoJS.enc.Hex.parse(WORDKEY)
CryptoJS.enc.Hex.parse(WORDKEY),
);
hashlink = hashlink.toString(CryptoJS.enc.Hex);
return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink;
@@ -216,7 +216,7 @@ export const getBasicSearch = (searchString) => {
.get(
BASE_URL +
"/api/endpoint/getbasicsearch_api?searchString=" +
searchString
searchString,
)
.then((res) => {
return res.data;
@@ -244,7 +244,7 @@ export const getIncidentbyID = (searchString) => {
.get(
BASE_URL +
"/api/endpoint/getincidentbyid_api?searchString=" +
searchString
searchString,
)
.then((res) => {
return res.data;
@@ -258,7 +258,7 @@ export const getIncidentbyID = (searchString) => {
export const getIsPublishedbyID = (searchString) => {
return axios
.get(
"/api/endpoint/getispublishedbyid_api?searchString=" + searchString
"/api/endpoint/getispublishedbyid_api?searchString=" + searchString,
)
.then((res) => {
return res.data;
@@ -274,7 +274,7 @@ export const getPartSavedAppeal = (searchString) => {
.get(
BASE_URL +
"/api/endpoint/getpartsavedappeal_api?searchString=" +
searchString
searchString,
)
.then((res) => {
return res.data;
@@ -290,7 +290,7 @@ export const getBasicDNSURLSearch = (searchString) => {
.get(
BASE_URL +
"/api/endpoint/getbasicdnsurlsearch_api?searchString=" +
searchString
searchString,
)
.then((res) => {
return res.data;
@@ -313,12 +313,24 @@ export const getSIPSEvents = async (caseid) => {
});
};
export const getSIPSMedia = async (caseid) => {
return axios
.get(BASE_URL + "/api/endpoint/getsipsmedia_api?caseid=" + caseid)
.then((res) => {
return res.data;
})
.catch((error) => {
consoleLogger(error);
return error.response;
});
};
export const getAddressSearchPaged = (
searchString,
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
showNumberOfRecords,
) => {
return axios
.get(
@@ -331,7 +343,7 @@ export const getAddressSearchPaged = (
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
showNumberOfRecords,
)
.then((res) => {
return res.data;
@@ -347,7 +359,7 @@ export const getBasicSearchPaged = (
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
showNumberOfRecords,
) => {
return axios
.get(
@@ -360,7 +372,7 @@ export const getBasicSearchPaged = (
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
showNumberOfRecords,
)
.then((res) => {
return res.data;
@@ -413,7 +425,7 @@ export const getBasicDNSSearchPaged = (
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
showNumberOfRecords,
) => {
return axios
.get(
@@ -424,7 +436,7 @@ export const getBasicDNSSearchPaged = (
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
showNumberOfRecords,
)
.then((res) => res.data)
.catch((error) => {
@@ -438,7 +450,7 @@ export const getAdvancedSearch = (searchString) => {
.get(
BASE_URL +
"/api/endpoint/getadvancedsearch_api?searchstring=" +
JSON.stringify(searchString)
JSON.stringify(searchString),
)
.then((res) => res.data)
.catch((error) => {
@@ -458,7 +470,7 @@ export const getAdvancedSearchPaged = (
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
showNumberOfRecords,
) => {
return axios
.get(
@@ -471,7 +483,7 @@ export const getAdvancedSearchPaged = (
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
showNumberOfRecords,
)
.then((res) => res.data)
.catch((error) => {
@@ -490,7 +502,7 @@ export const getBasicSearchDetails = async (
appealTypeName,
caseReference,
primaryIdAttribute,
incidentID
incidentID,
) => {
return axios
.get(
@@ -500,7 +512,7 @@ export const getBasicSearchDetails = async (
"&primaryIdAttribute=" +
primaryIdAttribute +
"&incidentID=" +
incidentID
incidentID,
)
.then((res) => res.data)
.catch((error) => {
@@ -512,7 +524,7 @@ export const getBasicPartSavedDetails = (
appealTypeName,
caseReference,
primaryIdAttribute,
incidentID
incidentID,
) => {
//console.log(
// "///////api call: ",
@@ -532,7 +544,7 @@ export const getBasicPartSavedDetails = (
"&primaryIdAttribute=" +
primaryIdAttribute +
"&incidentID=" +
incidentID
incidentID,
)
.then((res) => res.data)
.catch((error) => {
@@ -544,7 +556,7 @@ export const getBasicSearchDetailsPaged = async (
appealTypeName,
caseReference,
primaryIdAttribute,
incidentIDs
incidentIDs,
) => {
if (!incidentIDs || incidentIDs.length === 0) return [];
@@ -566,11 +578,11 @@ export const getBasicSearchDetailsPaged = async (
.join(" or ");
const url = `/api/endpoint/getbasicsearchdetailspaged_api?appealTypeName=${appealTypeName}&primaryIdAttribute=${primaryIdAttribute}&incidentID=${encodeURIComponent(
filter
filter,
)}`;
console.log(
`Fetching ${incidentIDs.length} incidents for appeal type: ${appealTypeName}`
`Fetching ${incidentIDs.length} incidents for appeal type: ${appealTypeName}`,
);
try {
@@ -586,7 +598,7 @@ export const getSearchDocumentDetails = (incidentID) => {
return axios
.get(
"/api/endpoint/getsearchdocumentdetails_api?incidentid=" +
incidentID
incidentID,
)
.then((res) => res.data)
.catch((error) => {
@@ -598,7 +610,7 @@ export const getSearchDocumentDetails = (incidentID) => {
export const getSearchDocumentTypes = (incidentID) => {
return axios
.get(
"/api/endpoint/getsearchdocumentTypes_api?incidentid=" + incidentID
"/api/endpoint/getsearchdocumentTypes_api?incidentid=" + incidentID,
)
.then((res) => res.data)
.catch((error) => {
@@ -611,7 +623,7 @@ export const getAppealPDFDocs = (incidentID) => {
.get(
BASE_URL +
"/api/endpoint/getappealpdfdocuments_api?incidentid=" +
incidentID
incidentID,
)
.then((res) => res.data)
.catch((error) => {
@@ -625,7 +637,7 @@ export const getSearchDocumentDetailsPaged = (
orderBy,
fieldSort,
showNumberOfRecords,
documentType
documentType,
) => {
//console.log("to api:", documentType);
return axios
@@ -641,7 +653,7 @@ export const getSearchDocumentDetailsPaged = (
"&showNumberOfRecords=" +
showNumberOfRecords +
"&documentType=" +
documentType
documentType,
)
.then((res) => res.data)
.catch((error) => {
@@ -653,7 +665,7 @@ export const getLinkedCases = (parentIncidentid) => {
return axios
.get(
"/api/endpoint/getlinkedcases_api?parentincidentid=" +
parentIncidentid
parentIncidentid,
)
.then((res) => {
return res.data;
@@ -745,7 +757,7 @@ export const getMandatoryFields = (whichForm) => {
.get(
BASE_URL +
"/api/endpoint/getmandatoryfields_api?whichForm=" +
whichForm
whichForm,
)
.then((res) => res.data)
.catch((error) => {
@@ -767,7 +779,7 @@ export const getPersonalAccount = (contactid) => {
.get(
BASE_URL +
"/api/endpoint/getpersonalaccount_api?contactid=" +
contactid
contactid,
)
.then((res) => {
return res.data;
@@ -787,7 +799,7 @@ export const getPersonalAccount = (contactid) => {
export const getAppealID = (
caseReference,
updateFormCollection,
primaryAttribute
primaryAttribute,
) => {
//console.log(
// "is this the collection?:",
@@ -803,11 +815,11 @@ export const getAppealID = (
"&primaryAttribute=" +
primaryAttribute +
"&caseReference=" +
caseReference
caseReference,
)
.then((res) => {
const result = Object.entries(res.data.value[0]).filter(
([key]) => !key.startsWith("_")
([key]) => !key.startsWith("_"),
)[0][1];
// console.log("result:", res.data);
var appealID = result;
@@ -824,7 +836,7 @@ export const getLogin = (emailAddress, pwd) => {
"/api/endpoint/getlogin_api?emailAddress=" +
emailAddress +
"&pwd=" +
pwd
pwd,
)
.then((res) => res.data)
.catch((error) => {
@@ -837,7 +849,7 @@ export const getMyCases = (loggedInUserId) => {
.get(
BASE_URL +
"/api/endpoint/getmycases_api?loggedInUserId=" +
loggedInUserId
loggedInUserId,
)
.then((res) => {
return res.data;
@@ -852,7 +864,7 @@ export const getMyInvolvements = async (loggedInUserId) => {
const res = await axios.get(
BASE_URL +
"/api/endpoint/getmyinvolvements_api?loggedInUserId=" +
loggedInUserId
loggedInUserId,
);
return res.data;
} catch (error) {
@@ -876,7 +888,7 @@ export const getMyRepresentations = (loggedInUserId) => {
.get(
BASE_URL +
"/api/endpoint/getmyrepresentations_api?loggedInUserId=" +
loggedInUserId
loggedInUserId,
)
.then((res) => res.data)
.catch((error) => {
@@ -888,7 +900,7 @@ export const getMyRepresentationsProxy = (loggedInUserId) => {
return axios
.get(
"/api/endpoint/getmyrepresentationsproxy_api?loggedInUserId=" +
loggedInUserId
loggedInUserId,
)
.then((res) => res.data)
.catch((error) => {
@@ -908,7 +920,8 @@ export const getRepresentations = (incidentID) => {
export const getRepresentationsProxy = (incidentID) => {
return axios
.get(
"/api/endpoint/getrepresentationsproxy_api?incidentID=" + incidentID
"/api/endpoint/getrepresentationsproxy_api?incidentID=" +
incidentID,
)
.then((res) => res.data)
.catch((error) => {
@@ -921,7 +934,7 @@ export const getWatchedCases = (loggedInUserId) => {
.get(
BASE_URL +
"/api/endpoint/getwatchedcases_api?loggedInUserId=" +
loggedInUserId
loggedInUserId,
)
.then((res) => res.data)
.catch((error) => {
@@ -933,7 +946,7 @@ export const getWatchedCasesProxy = (loggedInUserId) => {
return axios
.get(
"/api/endpoint/getwatchedcasesproxy_api?loggedInUserId=" +
loggedInUserId
loggedInUserId,
)
.then((res) => res.data)
.catch((error) => {
@@ -945,7 +958,7 @@ export const getAwaitingSubmissionProxy = (loggedInUserId) => {
return axios
.get(
"/api/endpoint/getawaitingsubmissionproxy_api?loggedInUserId=" +
loggedInUserId
loggedInUserId,
)
.then((res) => res.data)
.catch((error) => {
@@ -958,7 +971,7 @@ export const getAwaitingSubmission = (loggedInUserId) => {
.get(
BASE_URL +
"/api/endpoint/getawaitingsubmission_api?loggedInUserId=" +
loggedInUserId
loggedInUserId,
)
.then((res) => {
return res.data;
@@ -988,8 +1001,8 @@ export const getAwaitingSubmissionFromBlob = (containerName) => {
containerName +
hashAPIPath(
"/api/file/getawaitingsubmissionfromblob?container=" +
containerName
)
containerName,
),
)
.then((res) => {
return res.data;
@@ -1015,7 +1028,7 @@ export const getRepsFromBlob = (containerName) => {
BASE_URL +
"/api/file/getrepsblob?container=" +
containerName +
hashAPIPath("/api/file/getrepsblob?container=" + containerName)
hashAPIPath("/api/file/getrepsblob?container=" + containerName),
)
.then((res) => {
return res.data;
@@ -1042,7 +1055,7 @@ export const getRepsFromBlobProxy = async (containerName) => {
try {
const res = await axios.get(
// BASE_URL +
"/api/file/getrepsblobproxy?container=" + containerName
"/api/file/getrepsblobproxy?container=" + containerName,
// +
// hashAPIPath(
// "/api/file/getawaitingsubmissionfromblob?container=" +
@@ -1061,14 +1074,14 @@ export const getAwaitingSubmissionFromBlobProxy = async (containerName) => {
containerName,
"/api/file/getawaitingsubmissionfromblobproxy?container=" +
containerName,
"\n///////////////////////\n"
"\n///////////////////////\n",
);
try {
const res = await axios.get(
BASE_URL +
"/api/file/getawaitingsubmissionfromblobproxy?container=" +
containerName
containerName,
// +
// hashAPIPath(
// "/api/file/getawaitingsubmissionfromblob?container=" +
@@ -1129,7 +1142,7 @@ export const getPortalModuleDetailsProxy = (appealType, caseReference) => {
"/api/endpoint/getportalmoduledetailsproxy_api?appealType=" +
appealType +
"&caseReference=" +
caseReference.replace(/\'/g, "''")
caseReference.replace(/\'/g, "''"),
)
.then((res) => res.data)
.catch((error) => {
@@ -1141,7 +1154,7 @@ export const getEmailAccountCheck = (emailAddress) => {
return axios
.get(
"/api/endpoint/getemailaccountcheck_api?emailAddress=" +
emailAddress
emailAddress,
)
.then((res) => res.data)
.catch((error) => {
@@ -1213,7 +1226,7 @@ export const createNewCase = (
lpaID,
contactid,
createBody,
containerName
containerName,
) => {
appealTypeId = parseInt(appealTypeId);
@@ -1250,7 +1263,7 @@ export const createNewCaseBlob = (
contactid,
createBody,
containerName,
lpaName
lpaName,
) => {
appealTypeId = parseInt(appealTypeId);
@@ -1302,14 +1315,14 @@ export const updateCase = async (
updateBody,
updateFormCollection,
primaryAttribute,
caseReference
caseReference,
) => {
var data = updateBody;
var appealObj = await getAppealID(
caseReference,
updateFormCollection,
primaryAttribute
primaryAttribute,
);
var queryUrl =
@@ -1340,14 +1353,14 @@ export const updateCaseBlob = async (
updateBody,
updateFormCollection,
primaryAttribute,
caseReference
caseReference,
) => {
var data = updateBody;
var appealObj = await getAppealID(
caseReference,
updateFormCollection,
primaryAttribute
primaryAttribute,
);
var queryUrl =
@@ -1415,7 +1428,7 @@ export const getCaseByID = (incidentID) => {
// );
return axios
.get(
BASE_URL + "/api/endpoint/getcasebyid_api?incidentID=" + incidentID
BASE_URL + "/api/endpoint/getcasebyid_api?incidentID=" + incidentID,
)
.then((res) => {
return res.data;
@@ -1473,7 +1486,7 @@ export const deleteAwaitingSubmissions = (incidentID) => {
export const deleteAwaitingSubmissionsFromBlob = (
containerID,
casefolderID
casefolderID,
) => {
var queryUrl =
"/api/file/deleteblobcase?container=" +
@@ -1499,7 +1512,7 @@ export const deleteAwaitingSubmissionsFromBlob = (
export const deleteMyRepresentationsFromBlob = (
containerID,
casefolderID,
repfile
repfile,
) => {
var queryUrl =
"/api/file/deleteblobrep?container=" +
@@ -1563,7 +1576,7 @@ export const uploadFiles = async (
formValues,
filesObj,
containerID,
casefolderID
casefolderID,
) => {
let files = filesObj;
@@ -1644,7 +1657,7 @@ export const uploadRepFiles = async (
formValues,
filesObj,
containerID,
casefolderID
casefolderID,
) => {
let files = filesObj;
@@ -1721,7 +1734,7 @@ export const generateAppealPDF = async (
containerID,
casefolderID,
appealType,
fileList
fileList,
) => {
//let files = filesObj;
@@ -1779,8 +1792,8 @@ export const getFilesFromBlob = (containerName, casefolderID) => {
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
casefolderID
)
casefolderID,
),
)
.then((res) => {
//console.log("//////////----", res.data);
@@ -1798,7 +1811,7 @@ export const getFilesFromBlobproxy = (containerName, casefolderID) => {
"/api/file/getbloblistproxy?container=" +
containerName +
"&casefolderID=" +
casefolderID
casefolderID,
)
.then((res) => {
//console.log("//////////----", res.data);
@@ -1812,7 +1825,7 @@ export const getFilesFromBlobproxy = (containerName, casefolderID) => {
export const getFilesFromBlobHashed = (
containerName,
getblobshash,
casefolderID
casefolderID,
) => {
return axios
.get(
@@ -1820,7 +1833,7 @@ export const getFilesFromBlobHashed = (
containerName +
"&casefolderID=" +
casefolderID +
getblobshash
getblobshash,
)
.then((res) => {
//console.log("//////////----", res.data);
@@ -1835,7 +1848,7 @@ export const deleteBlob = async (
containerName,
blobName,
deleteblobhash,
casefolderID
casefolderID,
) => {
try {
const res = await axios.get(
@@ -1845,7 +1858,7 @@ export const deleteBlob = async (
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blobName) +
deleteblobhash
deleteblobhash,
);
return res.data;
} catch (error) {
@@ -1858,7 +1871,7 @@ export const deleteRepBlob = async (
blobName,
deleteblobhash,
casefolderID,
filenamePrefix
filenamePrefix,
) => {
try {
const res = await axios.get(
@@ -1868,7 +1881,7 @@ export const deleteRepBlob = async (
encodeURIComponent(casefolderID + "/" + filenamePrefix) +
"&blobname=" +
encodeURIComponent(blobName) +
deleteblobhash
deleteblobhash,
);
return res.data;
} catch (error) {
@@ -1884,14 +1897,14 @@ export const downloadBlob = (containerName, blobName) => {
containerName.toLowerCase() +
"&blobname=" +
blobName,
{ responseType: "blob" }
{ responseType: "blob" },
)
.then((response) => {
//console.log("Downloaded blob content:");
res.setHeader(
"content-disposition",
"attachment; filename=" + blobName
"attachment; filename=" + blobName,
);
//console.log("has downloaded");
@@ -1928,8 +1941,8 @@ export const getProgressFromBlob = async (containerName, casereference) => {
"/api/file/getprogressobjblob?container=" +
encodeURIComponent(containerName) +
"&casefolderID=" +
encodeURIComponent(casereference)
)
encodeURIComponent(casereference),
),
);
return res.data;
} catch (error) {
@@ -1941,7 +1954,7 @@ export const sendEmail = async (
templateId,
emailAddress,
personalisation,
reference
reference,
) => {
var mailData = {
"templateId": templateId,
@@ -2042,7 +2055,7 @@ export const createContainerProxy = (containerName) => {
export const sendCaseCompleteMessage = async (
containerID,
caseReference,
inv
inv,
) => {
var queryUrl =
"/api/file/createappealcompletemessage_api?container=" +
@@ -2071,7 +2084,7 @@ export const sendCaseCompleteMessage = async (
export const sendCaseCompleteMessageProxy = async (
containerID,
caseReference
caseReference,
) => {
var queryUrl =
"/api/file/createappealcompletemessageproxy_api?container=" +
@@ -2098,7 +2111,7 @@ export const sendCaseCompleteMessageProxy = async (
export const sendRepCompleteMessage = async (
containerID,
caseReference,
fileName
fileName,
) => {
var queryUrl =
"/api/file/createrepcompletemessage_api?container=" +
@@ -2210,7 +2223,7 @@ export const getPreferredLanguage = async (email) => {
.get(
BASE_URL +
"/api/endpoint/getpreferredlanguage_api?emailAddress=" +
email
email,
)
.then((res) => {
return res.data;
@@ -2226,7 +2239,7 @@ export const getAppealPDFDocument = async (incidentid) => {
.get(
BASE_URL +
"/api/endpoint/getappealpdfdocuments_api?incidentid=" +
incidentid
incidentid,
)
.then((res) => {
return res.data;
@@ -2251,7 +2264,7 @@ export const getNewAppealsPage = async (
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
showNumberOfRecords,
) => {
return axios
.get(
@@ -2264,7 +2277,7 @@ export const getNewAppealsPage = async (
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
showNumberOfRecords,
)
.then((res) => {
return res.data;
@@ -2282,7 +2295,7 @@ export const getNewDocumentsPaged = async (
showNumberOfRecords,
documentType,
documentOrigin,
selectedWeeks
selectedWeeks,
) => {
console.log(
"/api/admin/getlatestdocuments_api?pageNumber=" +
@@ -2298,7 +2311,7 @@ export const getNewDocumentsPaged = async (
"&numberWeeks=" +
selectedWeeks +
"&documentOrigin=" +
documentOrigin
documentOrigin,
);
return axios
.get(
@@ -2315,7 +2328,7 @@ export const getNewDocumentsPaged = async (
"&numberWeeks=" +
selectedWeeks +
"&documentOrigin=" +
documentOrigin
documentOrigin,
)
.then((res) => {
return res.data;
+2
View File
@@ -41,7 +41,9 @@ const Case = (props) => {
docsOffline={props.props.docsOffline}
documentDetailsObj={props.documentDetailsObj}
showFilteredDocs={props.showFilteredDocs}
showLoginCheck={props.showLoginCheck}
eventsObj={props.eventsObj}
mediaObj={props.mediaObj}
/>
{showRepresentations == true && (
<RepresentationList
+646
View File
@@ -0,0 +1,646 @@
// // import { setEventDetails } from "../../store/searchOutput/action";
// // import { setCurrentPage } from "../../store/currentView/action";
// // import { connect } from "react-redux";
// // import useTranslation from "next-translate/useTranslation";
// // import { formatDates } from "../utils";
// // const MediaDetails = (props) => {
// // let { t } = useTranslation();
// // const MediaView = (mediaArr) => {
// // mediaArr = mediaArr.value;
// // return mediaArr.map((item, key) => {
// // <div>{item.pinswg_name}</div>;
// // });
// // };
// // const mediaArr = props.mediaObj?.value || [];
// // // event publishiing flag set to true displa
// // function hasOwnPropertyAndNotNull(obj, property) {
// // return obj.hasOwnProperty(property) && obj[property] !== null;
// // }
// // //console.log(eventsArr.length > 0);
// // return (
// // mediaArr.length > 0 && (
// // <div className="govuk-grid-row">
// // <div className="card">
// // <div className="card-body">
// // <h2 className="govuk-heading-m ">
// // {t("case:event-title")}Media
// // </h2>
// // {mediaArr.map(
// // (item, key) =>
// // item.pinswg_publishtoweb && (
// // <>
// // {" "}
// // <div className="">
// // <div className="">
// // <div>
// // <b>
// // {" "}
// // {t(
// // "case:event-name-label",
// // )}
// // </b>
// // : {item.pinswg_name}
// // </div>
// // {hasOwnPropertyAndNotNull(
// // item,
// // "pinswg_description",
// // ) && (
// // <div>
// // <b>
// // {t(
// // "case:event-event-name-label",
// // )}
// // </b>
// // :{" "}
// // {
// // item.pinswg_description
// // }
// // </div>
// // )}
// // </div>
// // </div>
// // </>
// // ),
// // )}{" "}
// // </div>
// // </div>
// // </div>
// // )
// // );
// // };
// // const mapStateToProps = (state) => {
// // return {
// // ...state,
// // };
// // };
// // const mapDispatchToProps = (dispatch) => {
// // return {
// // setEventDetails: (mediaObj) => {
// // dispatch(setEventDetails(mediaObj));
// // },
// // setCurrentPage: (currentPage) => {
// // dispatch(setCurrentPage(currentPage));
// // },
// // };
// // };
// // export default connect(mapStateToProps, mapDispatchToProps)(MediaDetails);
// import { useEffect, useMemo, useState } from "react";
// import { connect } from "react-redux";
// import useTranslation from "next-translate/useTranslation";
// import { useRouter } from "next/router";
// const extractYouTubeId = (input) => {
// if (!input) return null;
// if (/^[a-zA-Z0-9_-]{11}$/.test(input)) return input;
// try {
// const url = new URL(input);
// if (url.hostname.includes("youtu.be"))
// return url.pathname.slice(1) || null;
// const v = url.searchParams.get("v");
// if (v) return v;
// const embedMatch = url.pathname.match(/\/embed\/([^/]+)/);
// if (embedMatch?.[1]) return embedMatch[1];
// const shortsMatch = url.pathname.match(/\/shorts\/([^/]+)/);
// if (shortsMatch?.[1]) return shortsMatch[1];
// return null;
// } catch {
// return null;
// }
// };
// export default function MediaDetails({ mediaObj, whichTab }) {
// const router = useRouter();
// const mediaArr = mediaObj?.value || [];
// const videoItems = useMemo(() => {
// const isWelsh = router.locale === "cy";
// return (mediaArr || [])
// .filter((x) => x?.pinswg_publishtoweb)
// .map((item) => {
// const url = isWelsh
// ? item.pinswg_mediaLinkCY
// : item.pinswg_mediaLinkEN;
// const videoId = extractYouTubeId(url);
// if (!videoId) return null;
// return {
// key:
// item.pinswg_documentid ||
// `${item.pinswg_name}-${videoId}`,
// videoId,
// name: item.pinswg_name || "Video",
// description: item.pinswg_description || "",
// thumbnail: `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,
// };
// })
// .filter(Boolean);
// }, [mediaArr, router.locale]);
// const [watchedIds, setWatchedIds] = useState(new Set());
// const [selectedIndex, setSelectedIndex] = useState(0);
// // Forces a fresh iframe instance whenever we need to hard-stop playback
// const [playerInstance, setPlayerInstance] = useState(0);
// const isActive = whichTab === "case-media";
// const selected = videoItems[selectedIndex] || null;
// // When leaving the media tab, hard-stop playback by unmounting (and bumping instance)
// useEffect(() => {
// if (!isActive) {
// setPlayerInstance((n) => n + 1);
// }
// }, [isActive]);
// // When list/locale changes, reset to first (no autoplay)
// useEffect(() => {
// setSelectedIndex(0);
// setPlayerInstance((n) => n + 1);
// }, [videoItems.length, router.locale]);
// if (!videoItems.length) return null;
// const embedBase = "https://www.youtube-nocookie.com/embed";
// // IMPORTANT:
// // - autoplay=0 for initial load
// // - autoplay=1 only when user clicks an item
// const [autoplay, setAutoplay] = useState(false);
// useEffect(() => {
// // whenever we leave tab or reset player, don't autoplay next time
// if (!isActive) setAutoplay(false);
// }, [isActive]);
// const embedSrc = selected
// ? `${embedBase}/${selected.videoId}?rel=0&modestbranding=1&playsinline=1&autoplay=${autoplay ? "1" : "0"}`
// : "";
// return (
// <div className="govuk-grid-row">
// <div className="card">
// <div className="card-body">
// <h2 className="govuk-heading-m">Media</h2>
// {/* Player: only render when tab is active */}
// {isActive && (
// <div className="videoWrapper govuk-!-margin-bottom-4">
// <iframe
// key={`${selected?.videoId}-${playerInstance}`}
// src={embedSrc}
// title={selected?.name || "YouTube video"}
// frameBorder="0"
// allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
// allowFullScreen
// />
// </div>
// )}
// <h3 className="govuk-heading-s">Videos</h3>
// <ul className="govuk-list govuk-list--spaced">
// {videoItems.map((v, idx) => {
// const isSelected = idx === selectedIndex;
// return (
// <li key={v.key}>
// <button
// type="button"
// className={`videoTabButton ${isSelected ? "active" : ""}`}
// onClick={() => {
// setSelectedIndex(idx);
// setAutoplay(true); // play only on user action
// setPlayerInstance((n) => n + 1); // ensure clean switch / stop previous
// setWatchedIds((prev) =>
// new Set(prev).add(v.videoId),
// );
// }}
// >
// <div className="videoTabContent">
// <div className="thumbnailWrapper">
// <img
// src={v.thumbnail}
// alt=""
// className="videoThumbnail"
// loading="lazy"
// />
// <span
// className="playIcon"
// aria-hidden="true"
// >
// ▶
// </span>
// {/* Duration badge (see section 2) */}
// {v.duration && (
// <span className="durationBadge">
// {v.duration}
// </span>
// )}
// {/* Watched indicator (see section 3) */}
// {watchedIds.has(v.videoId) && (
// <span className="watchedBadge">
// Watched
// </span>
// )}
// </div>
// <span className="videoTitle">
// {v.name}
// </span>
// </div>
// </button>
// </li>
// );
// })}
// </ul>
// </div>
// </div>
// <style jsx>{`
// .videoWrapper {
// position: relative;
// padding-bottom: 56.25%;
// height: 0;
// overflow: hidden;
// border: 1px solid #b1b4b6;
// border-radius: 4px;
// }
// .videoWrapper iframe {
// position: absolute;
// top: 0;
// left: 0;
// width: 100%;
// height: 100%;
// }
// .videoTabButton {
// width: 100%;
// text-align: left;
// background: transparent;
// border: 1px solid #b1b4b6;
// border-radius: 4px;
// padding: 10px 12px;
// cursor: pointer;
// }
// .videoTabButton:hover {
// background: #f3f2f1;
// }
// .videoTabButton.active {
// border-color: #1d70b8;
// box-shadow: inset 0 0 0 1px #1d70b8;
// background: #eef4ff;
// }
// .videoTabContent {
// display: flex;
// gap: 12px;
// align-items: center;
// }
// .videoThumbnail {
// width: 120px;
// aspect-ratio: 16 / 9;
// object-fit: cover;
// border-radius: 4px;
// border: 1px solid #b1b4b6;
// background: #000;
// }
// .videoTitle {
// font-weight: 600;
// }
// @media (max-width: 40.0625em) {
// .videoThumbnail {
// width: 90px;
// }
// }
// .thumbnailWrapper {
// position: relative;
// width: 120px;
// flex-shrink: 0;
// }
// .videoThumbnail {
// width: 100%;
// aspect-ratio: 16 / 9;
// object-fit: cover;
// border-radius: 4px;
// border: 1px solid #b1b4b6;
// background: #000;
// }
// /* ▶ Play icon */
// .playIcon {
// position: absolute;
// inset: 0;
// display: flex;
// align-items: center;
// justify-content: center;
// font-size: 32px;
// color: white;
// text-shadow: 0 0 6px rgba(0, 0, 0, 0.7);
// pointer-events: none;
// }
// /* ⏱ Duration badge */
// .durationBadge {
// position: absolute;
// bottom: 6px;
// right: 6px;
// background: rgba(0, 0, 0, 0.8);
// color: white;
// font-size: 12px;
// padding: 2px 6px;
// border-radius: 3px;
// }
// /* 👁 Watched badge */
// .watchedBadge {
// position: absolute;
// top: 6px;
// left: 6px;
// background: #00703c; /* GOV green */
// color: white;
// font-size: 11px;
// padding: 2px 6px;
// border-radius: 3px;
// }
// .videoTabContent {
// display: flex;
// gap: 12px;
// align-items: center;
// }
// .videoTitle {
// font-weight: 600;
// }
// `}</style>
// </div>
// );
// }
import { useEffect, useMemo, useState } from "react";
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
/**
* MediaDetails
* - Renders a YouTube player + a list of videos from mediaObj.value
* - Loads first item by default (no autoplay)
* - Plays only when user selects an item
* - Stops playback when leaving the "case-media" parent tab (pass whichTab)
* - Shows thumbnail with play overlay
* - Optional duration badge (if you supply v.duration)
* - "Watched" badge once a video has been played (session only)
*/
const extractYouTubeId = (input) => {
if (!input) return null;
// Already looks like a YouTube ID?
if (/^[a-zA-Z0-9_-]{11}$/.test(input)) return input;
try {
const url = new URL(input);
// youtu.be/<id>
if (url.hostname.includes("youtu.be")) {
return url.pathname.replace("/", "") || null;
}
// youtube.com/watch?v=<id>
const v = url.searchParams.get("v");
if (v) return v;
// youtube.com/embed/<id>
const embedMatch = url.pathname.match(/\/embed\/([^/]+)/);
if (embedMatch?.[1]) return embedMatch[1];
// youtube.com/shorts/<id>
const shortsMatch = url.pathname.match(/\/shorts\/([^/]+)/);
if (shortsMatch?.[1]) return shortsMatch[1];
return null;
} catch {
return null;
}
};
const MediaDetails = ({ mediaObj, whichTab, title, useNoCookie = true }) => {
const { t } = useTranslation();
const router = useRouter();
const mediaArr = mediaObj?.value || [];
const isActive = whichTab ? whichTab === "case-media" : true;
// Build list of playable items (publishable only)
const videoItems = useMemo(() => {
const isWelsh = router.locale === "cy";
return (mediaArr || [])
.filter((x) => x?.pinswg_publishtoweb)
.map((item) => {
const link = isWelsh
? item.pinswg_mediaLinkCY
: item.pinswg_mediaLinkEN;
const videoId = extractYouTubeId(link);
if (!videoId) return null;
return {
key:
item.pinswg_documentid ||
item.pinswg_mediaid ||
`${item.pinswg_name}-${videoId}`,
videoId,
name: item.pinswg_name || "Video",
description: item.pinswg_description || "",
// If you later store duration in CRM, map it here (e.g. "03:42")
duration: item.pinswg_duration || null,
thumbnailHQ: `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,
thumbnailMax: `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`,
raw: item,
};
})
.filter(Boolean);
}, [mediaArr, router.locale]);
const [selectedIndex, setSelectedIndex] = useState(0);
// Default false so first item loads but does not play.
const [autoplay, setAutoplay] = useState(false);
// Bump this to force iframe remount (hard stop playback).
const [playerInstance, setPlayerInstance] = useState(0);
// Track which videos have been played (session only).
const [watchedIds, setWatchedIds] = useState(() => new Set());
// When list or locale changes: reset selection and do not autoplay
useEffect(() => {
setSelectedIndex(0);
setAutoplay(false);
setPlayerInstance((n) => n + 1);
}, [videoItems.length, router.locale]);
// When leaving the media tab: hard stop playback
useEffect(() => {
if (!isActive) {
setAutoplay(false);
setPlayerInstance((n) => n + 1);
}
}, [isActive]);
const selected = videoItems[selectedIndex] || null;
const domain = useNoCookie
? "https://www.youtube-nocookie.com"
: "https://www.youtube.com";
const embedSrc = selected
? `${domain}/embed/${selected.videoId}?rel=0&modestbranding=1&playsinline=1&autoplay=${
autoplay ? "1" : "0"
}`
: "";
if (!videoItems.length) return null;
return (
<div className="govuk-grid-row">
<div className="card">
<div className="card-body">
<h2 className="govuk-heading-m">{t("case:media-title")}</h2>
{/* Player: unmount when not active (guarantees playback stops) */}
{isActive && (
<div className="videoWrapper govuk-!-margin-bottom-4">
<iframe
key={`${selected?.videoId}-${playerInstance}`}
src={embedSrc}
title={selected?.name || "YouTube video"}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
)}
{/* Selected info */}
{selected && (
<div className="govuk-!-margin-bottom-4">
<div className="govuk-body">
<b>{t("case:event-name-label")}</b>:{" "}
{selected.name}
</div>
{!!selected.description && (
<div className="govuk-body">
<b>{t("case:event-event-name-label")}</b>:{" "}
{selected.description}
</div>
)}
</div>
)}
{/* Video list */}
<h3 className="govuk-heading-s">
{t("case:video-heading")}
</h3>
<ul className="govuk-list govuk-list--spaced">
{videoItems.map((v, idx) => {
const isSelected = idx === selectedIndex;
return (
<li key={v.key}>
<button
type="button"
className={`videoTabButton ${
isSelected ? "active" : ""
}`}
aria-current={
isSelected ? "true" : undefined
}
onClick={() => {
setSelectedIndex(idx);
// Start playback only when user selects
setAutoplay(true);
// Force remount to stop previous + start clean
setPlayerInstance((n) => n + 1);
// Mark watched
setWatchedIds((prev) => {
const next = new Set(prev);
next.add(v.videoId);
return next;
});
}}
>
<div className="videoTabContent">
<div className="thumbnailWrapper">
<img
src={v.thumbnailMax}
onError={(e) => {
// fallback for videos without maxres
e.currentTarget.src =
v.thumbnailHQ;
}}
alt=""
className="videoThumbnail"
loading="lazy"
/>
<span
className="playIcon"
aria-hidden="true"
>
</span>
{!!v.duration && (
<span className="durationBadge">
{v.duration}
</span>
)}
{watchedIds.has(v.videoId) && (
<span className="watchedBadge">
Watched
</span>
)}
</div>
<span className="videoTitle">
{v.name}
</span>
</div>
</button>
</li>
);
})}
</ul>
</div>
</div>
</div>
);
};
export default MediaDetails;
+42 -3
View File
@@ -8,6 +8,7 @@ import { connect } from "react-redux";
import transLookup from "../../data/lookuptranslations.json";
import Documents from "./documents";
import EventsDetails from "./events";
import MediaDetails from "./media";
import {
createWatchedCases,
@@ -81,8 +82,10 @@ const CaseSummary = (props) => {
ticketnumber,
} = props;
const showLoginCheck =
currentType == "directResultsObj" ? "true" : currentView.showLogin;
// const showLoginCheck =
// currentType == "directResultsObj" ? "true" : currentView.showLogin;
let showLoginCheck = props.showLoginCheck;
const { data: session, status } = useSession();
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -416,7 +419,7 @@ const CaseSummary = (props) => {
>
<a
className="govuk-tabs__tab"
href="#case-events"
href="#case-eia"
onClick={() => setWhichTab("case-eia")}
>
{" "}
@@ -444,6 +447,26 @@ const CaseSummary = (props) => {
</a>
</li>
)}
{isObjectNotEmpty(props, "mediaObj") && (
<li
className={
whichTab == "case-media"
? "govuk-tabs__list-item govuk-tabs__list-item--selected"
: "govuk-tabs__list-item "
}
>
<a
className="govuk-tabs__tab"
href="#case-media"
onClick={() =>
setWhichTab("case-media")
}
>
{" "}
{t("case:summary-tab-media-label")}{" "}
</a>
</li>
)}
<li
className={
whichTab == "case-documents"
@@ -1741,6 +1764,22 @@ const CaseSummary = (props) => {
</div>
)}
{isObjectNotEmpty(props, "mediaObj") && (
<div
className={
whichTab == "case-media"
? "govuk-tabs__panel "
: "govuk-tabs__panel govuk-tabs__panel--hidden"
}
id="case-media"
>
<MediaDetails
mediaObj={props.mediaObj}
whichTab={whichTab}
/>
</div>
)}
<div
className={
whichTab == "case-documents"
+3 -2
View File
@@ -64,6 +64,7 @@ const Header = (props) => {
"/myportal/contactus",
"/unsubscribe/[watchlistid]",
"/unsubscribeall/[watchlistid]",
"/status",
];
const hasContactLink = [
@@ -84,14 +85,14 @@ const Header = (props) => {
const handleLogout = () => {
console.info("////////////\n" + "logout" + "\n////////////");
window.localStorage.clear(),
(window.localStorage.clear(),
destroyCookie(null, "next-auth.csrf-token", { path: "/" }),
destroyCookie(null, "next-auth.callback-url", { path: "/" }),
destroyCookie(null, "pedw_locale", { path: "/" }),
destroyCookie(null, "pinsUser", { path: "/" }),
signOut({
callbackUrl: locale == "cy" ? "/cy/allgofnodi" : "/logout",
});
}));
};
//const switchLocale = locale === "en" ? "cy" : "en";
+3 -1
View File
@@ -24,8 +24,10 @@ const MainHome = (props) => {
<div className="flex-container grid-row govuk-body ">
{showLogin != "true" && (
<>
<LoginSoon />
{/* <LoginSoon />
<CaseSearch /> */}
<CaseSearch />
<Login session={session} siteurl={siteurl} />
</>
)}
{showLogin == "true" && (
+98 -26
View File
@@ -1,46 +1,118 @@
import useTranslation from "next-translate/useTranslation";
import router from "next/router";
import Link from "next/link";
import CaseNoticeBanner from "./case/caseNoticeBanner";
export default function ServiceBanner(props) {
let { t, lang } = useTranslation();
const showSigninFromEnv = true;
const showNoticeFromEnv = false;
// keep backwards-compat with existing usage
const showSignin = props?.noSignin === true ? false : showSigninFromEnv;
const signInButton = () => {
router.push(
`${
router.locale == "cy" ? "/awd/mewngofnodi" : "/auth/signin"
}?callbackUrl=${encodeURIComponent(
window.location.href
)}&error=EmailSignin`
window.location.href,
)}&error=EmailSignin`,
);
};
return (
<div className="servicebanner">
<h1>
<img
className={
router.locale == "cy"
? "govuk-!-margin-top-4 serviceTitle cy"
: "govuk-!-margin-top-4 serviceTitle"
}
src={
router.locale == "cy"
? "/assets/images/planning-casework-cy.svg"
: "/assets/images/planning-casework-en.svg"
}
alt="Planning casework"
/>
</h1>
{props.noSignin != true && (
<div className="serviceBanner_userDetails">
<button
onClick={() => signInButton()}
className="govuk-header__link "
<>
<div className="servicebanner">
<h1>
<img
className={
router.locale == "cy"
? "govuk-!-margin-top-4 serviceTitle cy"
: "govuk-!-margin-top-4 serviceTitle"
}
src={
router.locale == "cy"
? "/assets/images/planning-casework-cy.svg"
: "/assets/images/planning-casework-en.svg"
}
alt="Planning casework"
/>
</h1>
{showSignin && props.noSignin != true && (
<div className="serviceBanner_userDetails">
<button
onClick={() => signInButton()}
className="govuk-header__link "
>
{t("common:signin-label")}
</button>
</div>
)}
</div>
{showNoticeFromEnv && (
<div>
<div
className="govuk-warning-text noticebanner govuk-!-margin-bottom-0"
key={1}
>
{t("common:signin-label")}
</button>
<strong className="govuk-warning-text__text">
<span className="govuk-visually-hidden">
Announcement
</span>
</strong>
<div>
<div className=" govuk-body-s">
<h2 className="govuk-heading-s">
{router.locale == "cy"
? "Cyhoeddiad"
: "Announcement"}
</h2>
</div>
<div className="govuk-body-s">
{router.locale == "cy" ? (
<>
<p>
Oherwydd gwaith cynnal a chadw
hanfodol, rydym yn disgwyl rhywfaint
o darfu ar ein gwasanaethau Porth
Gwaith Achos rhwng 9-13 Chwefror
2026.{" "}
</p>
<p>
Ymddiheurwn am yr anghyfleustra. Am
ymholiadau brys yn ystod y cyfnod
hwn, cysylltwch â{" "}
<a href="mailto:pedw.gwaithachos@llyw.cymru">
pedw.gwaithachos@llyw.cymru
</a>
</p>
</>
) : (
<>
<p>
Due to essential maintenance, we are
expecting some disruption to our
Casework Portal services between
9-13 February 2026.
</p>
<p>
We apologise for the inconvenience.
For urgent enquries during this time
please contact{" "}
<a href="mailto:pedw.casework@gov.wales">
pedw.casework@gov.wales
</a>
</p>
</>
)}
</div>
</div>
</div>
</div>
)}
</div>
</>
);
}
+4 -1
View File
@@ -6,6 +6,7 @@
"summary-tab-map-label": " Map",
"summary-tab-events-label": " Digwyddiadau",
"summary-tab-documents-label": "Dogfennau",
"summary-tab-media-label": "Cyfryngau",
"summary-reference-label": "Cyfeirnod",
"summary-applicant-label": "Apelydd",
"summary-applicantonly-label": "Ymgeisydd",
@@ -139,5 +140,7 @@
"summary-reporting-decision-sub-title": "Adrodd/Penderfyniad",
"summary-report-issued-to-welsh-ministers": "Adroddiad a Gyhoeddwyd i Weinidogion Cymru",
"summary-decision-issued-to-applicant": "Penderfyniad a Gyhoeddwyd i'r Ymgeisydd",
"summary-decision-outcome": "Canlyniad y Penderfyniad"
"summary-decision-outcome": "Canlyniad y Penderfyniad",
"media-title": "Cyfryngau",
"video-heading": "Fideos"
}
+4 -1
View File
@@ -6,6 +6,7 @@
"summary-tab-map-label": "Map",
"summary-tab-events-label": "Events",
"summary-tab-documents-label": "Documents",
"summary-tab-media-label": "Media",
"summary-reference-label": "Reference",
"summary-applicant-label": "Appellant",
"summary-applicantonly-label": "Applicant",
@@ -140,5 +141,7 @@
"summary-reporting-decision-sub-title": "Reporting/Decision",
"summary-report-issued-to-welsh-ministers": "Report Issued to Welsh Ministers",
"summary-decision-issued-to-applicant": "Decision Issued to Applicant",
"summary-decision-outcome": "Decision Outcome"
"summary-decision-outcome": "Decision Outcome",
"media-title": "Media",
"video-heading": "Fideos"
}
+6 -6
View File
@@ -9,12 +9,12 @@ export function middleware(request) {
process.env.NODE_ENV === "production" ? "" : `'unsafe-eval'`
};
style-src 'self' 'unsafe-eval' 'unsafe-inline';
img-src 'self' 'unsafe-inline' https://www.gov.wales https://gov.wales https://fonts.gstatic.com https://www.googletagmanager.com blob: data:;
img-src 'self' 'unsafe-inline' https://www.gov.wales https://gov.wales https://fonts.gstatic.com https://www.googletagmanager.com blob: data: https://img.youtube.com;
connect-src 'self' http://127.0.0.1 https://uksouth-1.in.applicationinsights.azure.com https://ukwest-0.in.applicationinsights.azure.com https://js.monitor.azure.com https://region1.google-analytics.com;
font-src 'self' https://pro.fontawesome.com/ https://fonts.gstatic.com/ ;
object-src 'none';
base-uri 'self';
frame-src https://datamap.gov.wales/;
frame-src https://datamap.gov.wales/ https://www.youtube.com https://www.youtube-nocookie.com;
form-action 'self' ` +
process.env.NEXTAUTH_URL +
`;
@@ -49,7 +49,7 @@ export function middleware(request) {
// CSP policy
requestHeaders.set(
"Content-Security-Policy",
contentSecurityPolicyHeaderValue
contentSecurityPolicyHeaderValue,
);
const response = NextResponse.next({
@@ -59,7 +59,7 @@ export function middleware(request) {
});
response.headers.set(
"Content-Security-Policy",
contentSecurityPolicyHeaderValue
contentSecurityPolicyHeaderValue,
);
// XSS protection (legacy)
@@ -77,13 +77,13 @@ export function middleware(request) {
// Permissions lockdown
response.headers.set(
"Permissions-Policy",
"geolocation=(), camera=(), microphone=(), fullscreen=(self)"
"geolocation=(), camera=(), microphone=(), fullscreen=(self)",
);
// Enforce HTTPS via HSTS
response.headers.set(
"Strict-Transport-Security",
"max-age=63072000; includeSubDomains; preload"
"max-age=63072000; includeSubDomains; preload",
);
return response;
+149
View File
@@ -0,0 +1,149 @@
import axios from "axios";
import CryptoJS from "crypto-js";
import { azureHeaders, consoleLogger, getToken } from "../../../actions";
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 caseid = req.query.caseid;
// var token = await getToken();
// var queryUrl =
// "pinswg_sipsevents?$filter=_pinswg_sipseventsid_value eq " +
// caseid +
// "&$count=true";
// return axios
// .get(
// WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
// azureHeaders(token.access_token),
// )
// .then(({ data }) => {
// res.status(200).json(data);
// })
// .catch((error) => {
// consoleLogger(error);
// res.status(400).json(error);
// });
// }
export default function ApiProx(req, res) {
const data = {
"@odata.count": 4,
"value": [
{
"pinswg_publishtoweb@OData.Community.Display.V1.FormattedValue":
"Yes",
"pinswg_publishtoweb": true,
"pinswg_uploadstatus": 846040000,
"pinswg_name":
"Puffin Paradise? Exploring Wales Cutest Wildlife Spot (Skomer Travel Guide)",
"pinswg_description":
"Skomer Island is a tiny uninhabited isle off the coast of Pembrokeshire in western Wales. Although it's just ten minutes from the mainland by ferry, it's home to over 40,000 puffins, the largest colony in the world of Manx Shearwaters, and lots of seals.",
"pinswg_latestpublishedversion": "1.0",
"pinswg_latestpublisheddate@OData.Community.Display.V1.FormattedValue":
"2/2/2026",
"pinswg_latestpublisheddate": "2026-02-02T10:20:46Z",
"pinswg_documentpublisheddate": "2026-02-02T10:20:46Z",
"pinswg_documentid": "9d4550b7-2000-f111-aa10-002248008e24",
"createdon@OData.Community.Display.V1.FormattedValue":
"2/2/2026 10:19 AM",
"createdon": "2026-02-02T10:19:55Z",
"pinswg_hashlink":
"/api/documents/download/A22258739?hash=51d64c1330ed2fe1fb9aff4658b8eedadf1b062d2b0313b39bc5af786228324b",
"pinswg_mediaLinkEN":
"https://www.youtube.com/watch?v=H7S6CfF_7PI",
"pinswg_mediaLinkCY":
"https://www.youtube.com/watch?v=H7S6CfF_7PI",
},
{
"pinswg_publishtoweb@OData.Community.Display.V1.FormattedValue":
"Yes",
"pinswg_publishtoweb": true,
"pinswg_uploadstatus": 846040000,
"pinswg_name": "Redstone Cross - side roads",
"pinswg_description": "Redstone Cross - side roads",
"pinswg_latestpublishedversion": "1.0",
"pinswg_latestpublisheddate@OData.Community.Display.V1.FormattedValue":
"2/2/2026",
"pinswg_latestpublisheddate": "2026-02-02T10:20:46Z",
"pinswg_documentpublisheddate": "2026-02-02T10:20:46Z",
"pinswg_documentid": "9d4550b7-2000-f111-aa10-002248008e24",
"createdon@OData.Community.Display.V1.FormattedValue":
"2/2/2026 10:19 AM",
"createdon": "2026-02-02T10:19:55Z",
"pinswg_hashlink":
"/api/documents/download/A22258739?hash=51d64c1330ed2fe1fb9aff4658b8eedadf1b062d2b0313b39bc5af786228324b",
"pinswg_mediaLinkEN":
"https://www.youtube.com/watch?v=OhxeLV_jPyE",
"pinswg_mediaLinkCY":
"https://www.youtube.com/watch?v=OhxeLV_jPyE",
},
{
"pinswg_publishtoweb@OData.Community.Display.V1.FormattedValue":
"Yes",
"pinswg_publishtoweb": true,
"pinswg_uploadstatus": 846040000,
"pinswg_name":
"Apprenticeships: Dinorwig Power Station (bilingual)",
"pinswg_description": "blah blah blah",
"pinswg_latestpublishedversion": "1.0",
"pinswg_latestpublisheddate@OData.Community.Display.V1.FormattedValue":
"2/2/2026",
"pinswg_latestpublisheddate": "2026-02-02T10:20:46Z",
"pinswg_documentpublisheddate": "2026-02-02T10:20:46Z",
"pinswg_documentid": "9d4550b7-2000-f111-aa10-002248008e24",
"createdon@OData.Community.Display.V1.FormattedValue":
"2/2/2026 10:19 AM",
"createdon": "2026-02-02T10:19:55Z",
"pinswg_hashlink":
"/api/documents/download/A22258739?hash=51d64c1330ed2fe1fb9aff4658b8eedadf1b062d2b0313b39bc5af786228324b",
"pinswg_mediaLinkEN":
"https://www.youtube.com/watch?v=9nsmadjgW94",
"pinswg_mediaLinkCY":
"https://www.youtube.com/watch?v=9nsmadjgW94",
},
{
"pinswg_publishtoweb@OData.Community.Display.V1.FormattedValue":
"Yes",
"pinswg_publishtoweb": true,
"pinswg_uploadstatus": 846040000,
"pinswg_name":
"Climate Action Wales - Cardiff Cycle Workshop - Sustainable Cycling in Cardiff",
"pinswg_description": "blah blah blah",
"pinswg_latestpublishedversion": "1.0",
"pinswg_latestpublisheddate@OData.Community.Display.V1.FormattedValue":
"2/2/2026",
"pinswg_latestpublisheddate": "2026-02-02T10:20:46Z",
"pinswg_documentpublisheddate": "2026-02-02T10:20:46Z",
"pinswg_documentid": "9d4550b7-2000-f111-aa10-002248008e24",
"createdon@OData.Community.Display.V1.FormattedValue":
"2/2/2026 10:19 AM",
"createdon": "2026-02-02T10:19:55Z",
"pinswg_hashlink":
"/api/documents/download/A22258739?hash=51d64c1330ed2fe1fb9aff4658b8eedadf1b062d2b0313b39bc5af786228324b",
"pinswg_mediaLinkEN":
"https://www.youtube.com/watch?v=tRW3tm2Mby8",
"pinswg_mediaLinkCY":
"https://www.youtube.com/watch?v=tRW3tm2Mby8",
},
],
};
return res.status(200).json(data);
}
+10 -8
View File
@@ -84,7 +84,9 @@ const SignIn = (props) => {
<div className="govuk-body-m">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--l">
<h1 className="govuk-fieldset__heading govuk-heading-m">
{t("auth:auth-page-title")}{" "}
{t(
"auth:auth-page-title",
)}{" "}
</h1>
</legend>
@@ -122,7 +124,7 @@ const SignIn = (props) => {
htmlFor="email"
>
{t(
"auth:auth-email-label"
"auth:auth-email-label",
)}
</label>
@@ -142,7 +144,7 @@ const SignIn = (props) => {
}
>
{t(
"auth:auth-signin-button-label"
"auth:auth-signin-button-label",
)}
</button>
</div>
@@ -163,10 +165,10 @@ const SignIn = (props) => {
};
export async function getServerSideProps(context) {
if (process.env.SHOWLOGIN == false) {
if (process.env.SHOWLOGIN === "false" || process.env.SHOWLOGIN === false) {
return {
redirect: {
destination: "/404",
destination: "/status",
permanent: false,
},
};
@@ -177,7 +179,7 @@ export async function getServerSideProps(context) {
"\n//////////////////////\n",
"Sign in callbackurl: " + context.query.callbackUrl,
context.locale,
"\n//////////////////////\n"
"\n//////////////////////\n",
);
let formURL = context.req.headers.host;
//console.log("formUrl:", formURL);
@@ -207,7 +209,7 @@ export async function getServerSideProps(context) {
// Append the query parameters to the new URL
params.forEach((value, key) =>
toUrlObj.searchParams.append(key, value)
toUrlObj.searchParams.append(key, value),
);
return toUrlObj.toString(); // Return the full new URL as a string
@@ -216,7 +218,7 @@ export async function getServerSideProps(context) {
typeof context.query.callbackUrl != "undefined" &&
(formURL = appendParamsAndPathToNewUrl(
context.query.callbackUrl,
formURL
formURL,
));
//formURL = appendParamsAndPathToNewUrl(context.query.callbackUrl, formURL);
+3 -3
View File
@@ -236,10 +236,10 @@ export async function getServerSideProps(context) {
};
}
if (process.env.SHOWLOGIN == false) {
if (process.env.SHOWLOGIN === "false" || process.env.SHOWLOGIN === false) {
return {
redirect: {
destination: "/404",
destination: "/status",
permanent: false,
},
};
@@ -250,7 +250,7 @@ export async function getServerSideProps(context) {
"\n//////////////////////\n",
"Sign in callbackurl: " + context.req.headers.host,
context.locale,
csrfToken + "\n//////////////////////\n"
csrfToken + "\n//////////////////////\n",
);
let formURL = context.req.headers.host;
console.log("formUrl:", formURL);
+18 -6
View File
@@ -10,6 +10,7 @@ import {
getPersonalAccount,
getPortalLogin,
getSIPSEvents,
getSIPSMedia,
} from "../../actions";
import Breadcrumbs from "../../components/breadcrumbs";
import Case from "../../components/case";
@@ -24,6 +25,7 @@ import {
setEventDetails,
setSearchDetails,
setSearchResults,
setMediaDetails,
} from "../../store/searchOutput/action";
import { wrapper } from "../../store/store";
import ServiceBanner from "../../components/servicebanner";
@@ -104,11 +106,12 @@ const CaseHome = (props) => {
representationsObj={
props.searchResultsObj.representationsObj
}
showLoginCheck={true}
showLoginCheck={props.showLoginCheck}
messagesObj={props.messagesObj}
showFilteredDocs={props.showFilteredDocs}
showMaps={props.showMaps}
eventsObj={props.searchResultsObj.eventsObj}
mediaObj={props.searchResultsObj.mediaObj}
/>
</div>
<Footer
@@ -159,14 +162,22 @@ export const getServerSideProps = wrapper.getServerSideProps(
const searchDetailsObj = await getSearchDetails(searchResultsObj);
var eventsObj = {};
var mediaObj = {};
if (searchResultsObj.value[0].pinswg_appealcasetype == 846040002) {
eventsObj = await getSIPSEvents(
searchDetailsObj[0].value[0].pinswg_sipsid
searchDetailsObj[0].value[0].pinswg_sipsid,
);
store.dispatch(setEventDetails(eventsObj));
}
if (searchResultsObj.value[0].pinswg_appealcasetype == 846040002) {
mediaObj = await getSIPSMedia(
searchDetailsObj[0].value[0].pinswg_sipsid,
);
store.dispatch(setMediaDetails(mediaObj));
}
//console.log(
// "\n======================================\n",
// searchResultsObj,
@@ -182,7 +193,7 @@ export const getServerSideProps = wrapper.getServerSideProps(
console.log(
searchResultsObj["@odata.count"] > 1 ||
searchResultsObj["@odata.count"] < 1
searchResultsObj["@odata.count"] < 1,
);
if (searchResultsObj["@odata.count"] < 1) {
@@ -223,7 +234,7 @@ export const getServerSideProps = wrapper.getServerSideProps(
searchDetailsObj[0].value[0]
.pinswg_specialistcaseprocess
: "",
})
}),
);
}
}
@@ -234,17 +245,18 @@ export const getServerSideProps = wrapper.getServerSideProps(
searchResultsObj: searchResultsObj,
searchDetailsObj: searchDetailsObj,
eventsObj: eventsObj,
mediaObj: mediaObj,
},
currentType: "directResultsObj",
docsOffline: process.env.DOCAPI_OFFLINE || false,
messagesObj: await getCaseMessage(
searchResultsObj.value[0].incidentid
searchResultsObj.value[0].incidentid,
),
showFilteredDocs: process.env.SHOWFILTERDOCS || false,
showMaps: process.env.SHOWMAPS,
},
};
}
},
);
const mapStateToProps = (state) => {
+70 -5
View File
@@ -1,28 +1,93 @@
import useTranslation from "next-translate/useTranslation";
import Head from "next/head";
import CookieBanner from "../components/cookieBanner";
import Header from "../components/header";
import Footer from "../components/footer";
import router from "next/router";
const FourOhFour = (props) => {
const StatusPage = (props) => {
let { t, lang } = useTranslation();
const { footerLinks, pages } = props;
return (
<div>
<Head>
<title>Status Up</title>
<title>Site Status</title>
</Head>
<div id="page_wrapper">
<CookieBanner />
<Header />
<div className="govuk-width-container govuk-!-padding-bottom-9">
<main className="govuk-main-wrapper govuk-!-padding-top-9 govuk-!-padding-bottom-9">
<div className="govuk-grid-row">
<div className="govuk-grid-column-two-thirds ">
<h1>Status Up</h1>
<div
className="govuk-warning-text noticebanner govuk-!-margin-bottom-0"
key={1}
>
<strong className="govuk-warning-text__text">
<span className="govuk-visually-hidden">
Announcement
</span>
</strong>
<div>
<div className=" govuk-body-s">
<h2 className="govuk-heading-s">
{router.locale == "cy"
? "Cyhoeddiad"
: "Announcement"}
</h2>
</div>
<div className="govuk-body-s">
{router.locale == "cy" ? (
<>
<p>
Oherwydd gwaith cynnal a
chadw hanfodol, rydym yn
disgwyl rhywfaint o darfu ar
ein gwasanaethau Porth
Gwaith Achos rhwng 9-13
Chwefror 2026.{" "}
</p>
<p>
Ymddiheurwn am yr
anghyfleustra. Am ymholiadau
brys yn ystod y cyfnod hwn,
cysylltwch â{" "}
<a href="mailto:pedw.gwaithachos@llyw.cymru">
pedw.gwaithachos@llyw.cymru
</a>
</p>
</>
) : (
<>
<p>
Due to essential
maintenance, we are
expecting some disruption to
our Casework Portal services
between 9-13 February 2026.
</p>
<p>
We apologise for the
inconvenience. For urgent
enquries during this time
please contact{" "}
<a href="mailto:pedw.casework@gov.wales">
pedw.casework@gov.wales
</a>
</p>
</>
)}
</div>
</div>
</div>
</div>
</main>
</div>
<Footer footerLinks={footerLinks} ticketnumber={props} />
</div>
</div>
);
};
export default FourOhFour;
export default StatusPage;
+8
View File
@@ -8,6 +8,7 @@ export const searchResultsActionTypes = {
SETREPRESENTATIONS: "SETREPRESENTATIONS",
SETDNSCOORDS: "SETDNSCOORDS",
SETEVENTDETAILS: "SETEVENTDETAILS",
SETMEDIADETAILS: "SETMEDIADETAILS",
};
export const getSearchResultshObj = () => (dispatch) => {
@@ -63,3 +64,10 @@ export const setEventDetails = (eventsDetails) => (dispatch) => {
eventDetailsObj: eventsDetails,
});
};
export const setMediaDetails = (mediaDetails) => (dispatch) => {
return dispatch({
type: searchResultsActionTypes.SETMEDIADETAILS,
mediaDetailsObj: mediaDetails,
});
};
+6
View File
@@ -7,6 +7,7 @@ const searchResultsInitialState = {
representationsObj: {},
dnsCoordsObj: {},
eventDetailsObj: {},
mediaDetailsObj: {},
};
export default function reducer(state = searchResultsInitialState, action) {
@@ -42,6 +43,11 @@ export default function reducer(state = searchResultsInitialState, action) {
...state,
eventDetailsObj: action.eventDetailsObj,
};
case searchResultsActionTypes.SETMEDIADETAILS:
return {
...state,
mediaDetailsObj: action.mediaDetailsObj,
};
default:
return state;
}
+104
View File
@@ -3164,4 +3164,108 @@ ul.subFieldList {
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
line-height: 1.4;
}
#case-media {
.videoWrapper {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
border: 1px solid #b1b4b6;
border-radius: 4px;
}
.videoWrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.videoTabButton {
width: 100%;
text-align: left;
background: transparent;
border: 1px solid #b1b4b6;
border-radius: 4px;
padding: 10px 12px;
cursor: pointer;
}
.videoTabButton:hover {
background: #f3f2f1;
}
.videoTabButton.active {
border-color: #1d70b8;
box-shadow: inset 0 0 0 1px #1d70b8;
background: #eef4ff;
}
.videoTabContent {
display: flex;
gap: 12px;
align-items: center;
}
.thumbnailWrapper {
position: relative;
width: 120px;
flex-shrink: 0;
}
.videoThumbnail {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 4px;
border: 1px solid #b1b4b6;
background: #000;
}
.playIcon {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
color: white;
text-shadow: 0 0 6px rgba(0, 0, 0, 0.7);
pointer-events: none;
}
.durationBadge {
position: absolute;
bottom: 6px;
right: 6px;
background: rgba(0, 0, 0, 0.8);
color: white;
font-size: 12px;
padding: 2px 6px;
border-radius: 3px;
}
.watchedBadge {
position: absolute;
top: 6px;
left: 6px;
background: #00703c;
color: white;
font-size: 11px;
padding: 2px 6px;
border-radius: 3px;
}
.videoTitle {
font-weight: 600;
}
@media (max-width: 40.0625em) {
.thumbnailWrapper {
width: 90px;
}
}
}