refactor appeal for new and resume
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
// lib/forms/parseFormXml.js
|
||||
import xpath from "xpath";
|
||||
|
||||
/**
|
||||
* NOTE: Your current page uses DOMParser, which is available in the browser.
|
||||
* For SSR parsing you'd usually use xmldom or a similar package.
|
||||
*
|
||||
* This module supports BOTH:
|
||||
* - Browser: pass DOMParser output doc
|
||||
* - Server: if you later add xmldom, pass its doc here
|
||||
*/
|
||||
|
||||
export function getSectionCount(doc) {
|
||||
const tabs = xpath.select("/form/tabs/tab[*]", doc);
|
||||
return Array.isArray(tabs) ? tabs.length : 0;
|
||||
}
|
||||
|
||||
export function getTabTitles(doc) {
|
||||
// same XPath you had
|
||||
const titleAttrs = xpath.select(
|
||||
"//form/tabs/tab[*]/labels/label[@description]/@description",
|
||||
doc
|
||||
);
|
||||
|
||||
if (!Array.isArray(titleAttrs)) return [];
|
||||
// Attribute nodes differ by parser; be defensive:
|
||||
return titleAttrs
|
||||
.map((n) => (typeof n?.value === "string" ? n.value : n?.toString?.()))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function getTabs(doc) {
|
||||
const tabs = xpath.select("//form/tabs/tab[*]", doc);
|
||||
return Array.isArray(tabs) ? tabs : [];
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// lib/forms/readFormXml.js
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
* Reads /data/formsxml/{appealtypes}.xml and normalises it.
|
||||
* Returns { xmlStr } or throws on missing file.
|
||||
*/
|
||||
export function readFormXml(appealtypes) {
|
||||
if (!appealtypes) throw new Error("readFormXml: appealtypes is required");
|
||||
|
||||
const configDirectory = path.resolve(process.cwd(), "data/formsxml");
|
||||
const filePath = path.join(configDirectory, `${appealtypes}.xml`);
|
||||
|
||||
let xmlStr = fs.readFileSync(filePath, "utf8");
|
||||
|
||||
// Keep existing normalisation behaviour
|
||||
xmlStr = xmlStr.replace(/\t/g, "");
|
||||
xmlStr = xmlStr.replace(/\n/g, "");
|
||||
xmlStr = xmlStr.replace(/> <"/g, "><");
|
||||
xmlStr = xmlStr.toString();
|
||||
|
||||
return { xmlStr, filePath };
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// lib/myportal/hydrateMyPortalAppealStore.js
|
||||
import {
|
||||
setContainerID,
|
||||
setLoggedInUserEmail,
|
||||
setLoggedInUserId,
|
||||
setAccountDetails,
|
||||
} from "../../store/accountDetails/action";
|
||||
|
||||
import {
|
||||
setAppealLPA,
|
||||
setAppealType,
|
||||
setAppealTypeID,
|
||||
setCaseReference as setCaseReferenceAction,
|
||||
setFilesForAppeal,
|
||||
} from "../../store/appealType/action";
|
||||
|
||||
import {
|
||||
setAwaitingSubmissionDetails,
|
||||
setAwaitingSubmissionFromBlob,
|
||||
} from "../../store/awaitingSubmission/action";
|
||||
|
||||
import { setCurrentView, setLocale } from "../../store/currentView/action";
|
||||
import { setForm } from "../../store/formData/action";
|
||||
|
||||
/**
|
||||
* Hydrates Redux store for resume flow in /myportal/[appealtypes]
|
||||
*/
|
||||
export function hydrateMyPortalAppealStore(store, ctx, data) {
|
||||
const { query, locale } = ctx;
|
||||
|
||||
const {
|
||||
session,
|
||||
loggedInUser,
|
||||
loggedInUserEmail,
|
||||
appealTypeData,
|
||||
mandatoryFieldsData,
|
||||
pickListData,
|
||||
blobList,
|
||||
blobProgress,
|
||||
accountDetails,
|
||||
awaitingSubmissionFromBlob,
|
||||
xmlStr,
|
||||
} = data;
|
||||
|
||||
// Keep existing behaviour: write locale into store
|
||||
store.dispatch(setLocale(locale));
|
||||
|
||||
// Build caseReference in the same shape you already use
|
||||
const caseReference = {
|
||||
ticketnumber: query.casereference,
|
||||
incidentid: query.inid, // as per your file
|
||||
caseDetails: blobProgress,
|
||||
};
|
||||
|
||||
// Optional awaiting submission state setup
|
||||
if (awaitingSubmissionFromBlob) {
|
||||
store.dispatch(
|
||||
setAwaitingSubmissionFromBlob(awaitingSubmissionFromBlob)
|
||||
);
|
||||
store.dispatch(
|
||||
setAwaitingSubmissionDetails(awaitingSubmissionFromBlob)
|
||||
);
|
||||
store.dispatch(
|
||||
setCurrentView({
|
||||
viewName: "Awaiting Submission",
|
||||
viewKey: "awaitingSubmissionDetails",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
store.dispatch(setLoggedInUserId(loggedInUser));
|
||||
store.dispatch(setLoggedInUserEmail(loggedInUserEmail));
|
||||
store.dispatch(setAppealLPA(query.lpa));
|
||||
store.dispatch(setAppealTypeID(query.apt));
|
||||
store.dispatch(setCaseReferenceAction(caseReference));
|
||||
store.dispatch(setForm(xmlStr, mandatoryFieldsData, pickListData));
|
||||
store.dispatch(setAppealType(appealTypeData));
|
||||
store.dispatch(setContainerID(session.user.id));
|
||||
store.dispatch(setAccountDetails(accountDetails));
|
||||
|
||||
store.dispatch(setFilesForAppeal(blobList));
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// lib/myportal/loadMyPortalAppealPage.js
|
||||
import { getSession } from "next-auth/react";
|
||||
import {
|
||||
getAppealsTypesForNewAppeal,
|
||||
getAwaitingSubmissionFromBlob,
|
||||
getFilesFromBlob,
|
||||
getIP,
|
||||
getMandatoryFields,
|
||||
getPickLists,
|
||||
getProgressFromBlob,
|
||||
getPersonalAccount,
|
||||
} from "../../actions";
|
||||
|
||||
import { readFormXml } from "../forms/readFormXml";
|
||||
import { requireQueryParams } from "../routing/requireQueryParams";
|
||||
|
||||
/**
|
||||
* Loader for "resume appeal" in /myportal/[appealtypes]
|
||||
*/
|
||||
export async function loadMyPortalAppealPage(ctx) {
|
||||
const { query, req } = ctx;
|
||||
|
||||
getIP(req);
|
||||
|
||||
// Required params for resume flow (adjust if your routes always provide them)
|
||||
// casereference is the main identifier here
|
||||
const required = requireQueryParams(query, [
|
||||
"appealtypes",
|
||||
"apt",
|
||||
"casereference",
|
||||
]);
|
||||
if (!required.ok) {
|
||||
return { redirect: required.redirect };
|
||||
}
|
||||
|
||||
const session = await getSession(ctx);
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/auth/signin",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { cookies } = req;
|
||||
const loggedInUser = cookies?.pinsUser;
|
||||
const loggedInUserIdent = session.user.id;
|
||||
const loggedInUserEmail = session.user.email;
|
||||
|
||||
const accountDetails = await getPersonalAccount(loggedInUser);
|
||||
|
||||
// Fetch config + blob assets in parallel
|
||||
const [appealTypeData, mandatoryFieldsData, pickListData, blobList] =
|
||||
await Promise.all([
|
||||
getAppealsTypesForNewAppeal(),
|
||||
getMandatoryFields(query.appealtypes),
|
||||
getPickLists(query.appealtypes),
|
||||
getFilesFromBlob(loggedInUserIdent, query.casereference),
|
||||
]);
|
||||
|
||||
const blobProgress = await getProgressFromBlob(
|
||||
loggedInUserIdent,
|
||||
query.casereference
|
||||
);
|
||||
|
||||
// Optional: awaiting-submission view setup when query.key exists
|
||||
let awaitingSubmissionFromBlob = null;
|
||||
if (Object.prototype.hasOwnProperty.call(query, "key")) {
|
||||
awaitingSubmissionFromBlob = await getAwaitingSubmissionFromBlob(
|
||||
session.user.id
|
||||
);
|
||||
}
|
||||
|
||||
const { xmlStr } = readFormXml(query.appealtypes);
|
||||
|
||||
return {
|
||||
session,
|
||||
loggedInUser,
|
||||
loggedInUserIdent,
|
||||
loggedInUserEmail,
|
||||
appealTypeData,
|
||||
mandatoryFieldsData,
|
||||
pickListData,
|
||||
blobList,
|
||||
blobProgress,
|
||||
accountDetails,
|
||||
awaitingSubmissionFromBlob,
|
||||
xmlStr,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// lib/newappeal/hydrateNewAppealStore.js
|
||||
import {
|
||||
setContainerID,
|
||||
setLoggedInUserEmail,
|
||||
setLoggedInUserId,
|
||||
setAccountDetails,
|
||||
} from "../../store/accountDetails/action";
|
||||
import {
|
||||
setAppealLPA,
|
||||
setAppealType,
|
||||
setAppealTypeID,
|
||||
// IMPORTANT: avoid name collision in your page by importing as Action
|
||||
setCaseReference as setCaseReferenceAction,
|
||||
} from "../../store/appealType/action";
|
||||
import { setForm } from "../../store/formData/action";
|
||||
|
||||
/**
|
||||
* Dispatches all actions needed to hydrate Redux store for the page.
|
||||
*/
|
||||
export function hydrateNewAppealStore(store, query, data) {
|
||||
const {
|
||||
session,
|
||||
loggedInUser,
|
||||
loggedInUserEmail,
|
||||
appealTypeData,
|
||||
mandatoryFieldsData,
|
||||
pickListData,
|
||||
blobProgress,
|
||||
accountDetails,
|
||||
xmlStr,
|
||||
} = data;
|
||||
|
||||
const caseReference = {
|
||||
ticketnumber: query.id,
|
||||
incidentid: query.id,
|
||||
caseDetails: blobProgress,
|
||||
};
|
||||
|
||||
store.dispatch(setLoggedInUserId(loggedInUser));
|
||||
store.dispatch(setLoggedInUserEmail(loggedInUserEmail));
|
||||
store.dispatch(setAppealLPA(query.lpa));
|
||||
store.dispatch(setAppealTypeID(query.apt));
|
||||
store.dispatch(setCaseReferenceAction(caseReference));
|
||||
store.dispatch(setForm(xmlStr, mandatoryFieldsData, pickListData));
|
||||
store.dispatch(setAppealType(appealTypeData));
|
||||
store.dispatch(setContainerID(session.user.id));
|
||||
store.dispatch(setAccountDetails(accountDetails));
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// lib/newappeal/loadNewAppealPage.js
|
||||
import { getSession } from "next-auth/react";
|
||||
import {
|
||||
getAppealsTypesForNewAppeal,
|
||||
getIP,
|
||||
getMandatoryFields,
|
||||
getPickLists,
|
||||
getProgressFromBlob,
|
||||
getPersonalAccount,
|
||||
} from "../../actions";
|
||||
import { readFormXml } from "../forms/readFormXml";
|
||||
import { requireQueryParams } from "../routing/requireQueryParams";
|
||||
|
||||
/**
|
||||
* Loads everything needed for New Appeal SSR.
|
||||
* Returns { redirect } on auth/query issues, otherwise returns a data object.
|
||||
*/
|
||||
export async function loadNewAppealPage(ctx) {
|
||||
const { query, req } = ctx;
|
||||
|
||||
// Preserve existing behaviour
|
||||
getIP(req);
|
||||
|
||||
// Validate required params early (adjust keys if needed)
|
||||
const required = requireQueryParams(query, ["appealtypes", "apt", "id"]);
|
||||
if (!required.ok) {
|
||||
return { redirect: required.redirect };
|
||||
}
|
||||
|
||||
const session = await getSession(ctx);
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/auth/signin",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { cookies } = req;
|
||||
const loggedInUser = cookies?.pinsUser; // existing cookie usage
|
||||
const loggedInUserIdent = session.user.id;
|
||||
const loggedInUserEmail = session.user.email;
|
||||
|
||||
const [appealTypeData, mandatoryFieldsData, pickListData] =
|
||||
await Promise.all([
|
||||
getAppealsTypesForNewAppeal(),
|
||||
getMandatoryFields(query.appealtypes),
|
||||
getPickLists(query.appealtypes),
|
||||
]);
|
||||
|
||||
const blobProgress = await getProgressFromBlob(loggedInUserIdent, query.id);
|
||||
const accountDetails = await getPersonalAccount(loggedInUser);
|
||||
|
||||
const { xmlStr } = readFormXml(query.appealtypes);
|
||||
|
||||
return {
|
||||
session,
|
||||
loggedInUser,
|
||||
loggedInUserIdent,
|
||||
loggedInUserEmail,
|
||||
appealTypeData,
|
||||
mandatoryFieldsData,
|
||||
pickListData,
|
||||
blobProgress,
|
||||
accountDetails,
|
||||
xmlStr,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// lib/routing/requireQueryParams.js
|
||||
// Simple query validator for getServerSideProps
|
||||
|
||||
export function requireQueryParams(query, requiredKeys = []) {
|
||||
const missing = requiredKeys.filter((k) => !query?.[k]);
|
||||
if (missing.length === 0) return { ok: true };
|
||||
|
||||
// Redirect to a generic error page you already have
|
||||
// (you can swap this destination later)
|
||||
const destination = `/error?missing=${encodeURIComponent(
|
||||
missing.join(",")
|
||||
)}`;
|
||||
return {
|
||||
ok: false,
|
||||
redirect: {
|
||||
destination,
|
||||
permanent: false,
|
||||
},
|
||||
missing,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user