diff --git a/.env.example b/.env.example index 7ca014a84..ddc7f8b50 100644 --- a/.env.example +++ b/.env.example @@ -47,3 +47,6 @@ MAILGUN_SENDER=postmaster@mail.yourdomain.com # Base64 encoded PFX or p12 document signing certificate file ********************************************************************************************************************* PFX_BASE64= + +# Provide Pass pharse of above PFX or p12 document +PASS_PHRASE= diff --git a/.env.local_dev b/.env.local_dev index 7d0a4a251..6059c326e 100644 --- a/.env.local_dev +++ b/.env.local_dev @@ -51,3 +51,6 @@ MAILGUN_SENDER=postmaster@mail.yourdomain.com # Base64 encoded PFX or p12 document signing certificate file ********************************************************************************************************************* PFX_BASE64= + +# Provide Pass pharse of above PFX or p12 document +PASS_PHRASE= diff --git a/apps/OpenSign/package.json b/apps/OpenSign/package.json index 0b354b9c7..f460f70f7 100644 --- a/apps/OpenSign/package.json +++ b/apps/OpenSign/package.json @@ -40,8 +40,8 @@ "web-vitals": "^2.1.4" }, "scripts": { - "start": "mf-cra start", - "start-prod": "serve -s build", + "start": "serve -s build", + "start-dev": "mf-cra start", "build": "rm -rf public/mfbuild && npm run micro && CI=false && mf-cra build", "micro": "cd ../../microfrontends/SignDocuments && npm install && npm run build", "test": "react-scripts test", diff --git a/apps/OpenSign/src/App.js b/apps/OpenSign/src/App.js index 76ef93ee9..500e00808 100644 --- a/apps/OpenSign/src/App.js +++ b/apps/OpenSign/src/App.js @@ -14,6 +14,7 @@ import PageNotFound from "./routes/PageNotFound"; import ForgetPassword from "./routes/ForgetPassword"; import ChangePassword from "./routes/ChangePassword"; import ReportMicroapp from "./components/ReportMicroapp"; +import LoadMf from "./routes/LoadMf"; function App() { const [isloading, setIsLoading] = useState(true); @@ -59,6 +60,7 @@ function App() { } /> } /> + } /> } /> {process.env.REACT_APP_ENABLE_SUBSCRIPTION && ( <> diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 0bf111c1a..4bf02a818 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -93,7 +93,7 @@ const ReportTable = ({ {List?.length > 0 ? ( <> {currentLists.map((item, index) => ( - + {index + 1} {item?.Name} {item?.Note || "-"} @@ -153,7 +153,7 @@ const ReportTable = ({ )} -
+
{List.length > 10 && ( <> {currentPage > 1 && ( diff --git a/apps/OpenSign/src/routes/LoadMf.js b/apps/OpenSign/src/routes/LoadMf.js new file mode 100644 index 000000000..2f8a43a1f --- /dev/null +++ b/apps/OpenSign/src/routes/LoadMf.js @@ -0,0 +1,84 @@ +import { Suspense } from "react"; +import { useParams } from "react-router-dom"; +import useFederatedComponent from "mf-cra"; +import React from "react"; +import Title from "../components/Title"; +function RemoteApp({ app }) { + // console.log("app ", app); + const { Component: RemoteComponent } = useFederatedComponent(app); + // console.log("RemoteComponent ", RemoteComponent); + return ( + <> + + <Suspense + fallback={ + <div style={{ height: "300px" }}> + <div + style={{ + marginLeft: "45%", + marginTop: "150px", + fontSize: "45px", + color: "#3dd3e0" + }} + className="loader-37" + ></div> + </div> + } + > + <div + style={{ + boxShadow: "0 1px 3px 0 rgba(0, 0, 0, 0.33)", + backgroundColor: "#ffffff", + width: "100%", + overflow: "hidden", + borderRadius: 3, + minHeight: "70vh" + }} + > + {RemoteComponent && <RemoteComponent />} + </div> + </Suspense> + </> + ); +} +function checkObjectProperties(obj) { + // Check if all three properties exist in the object + if ( + obj && + obj.remoteName !== undefined && + obj.remoteUrl !== undefined && + obj.moduleToLoad !== undefined + ) { + return true; // All properties are present + } else { + return false; // At least one property is missing + } +} +export default function RemoteAppContainer() { + const { remoteApp } = useParams(); + + const obj = { + remoteUrl: window.location.origin + "/mfbuild/remoteEntry.js", + moduleToLoad: "./AppRoutes", + remoteName: remoteApp + }; + // console.log("obj ", obj); + const isMf = checkObjectProperties(obj); + if (isMf) { + const appToLoad = obj; + return <RemoteApp app={appToLoad} />; + } else { + return ( + <div className="flex items-center justify-center h-screen w-full bg-white rounded"> + <div className="text-center"> + <h1 className="text-[60px] lg:text-[120px] font-semibold text-black"> + 404 + </h1> + <p className="text-[30px] lg:text-[50px] text-black"> + Page Not Found + </p> + </div> + </div> + ); + } +} diff --git a/apps/OpenSign/src/routes/Login.js b/apps/OpenSign/src/routes/Login.js index bada3d29d..ba0d74133 100644 --- a/apps/OpenSign/src/routes/Login.js +++ b/apps/OpenSign/src/routes/Login.js @@ -744,7 +744,7 @@ function Login(props) { }; const GetLoginData = async () => { - setState({...state, loading: true}) + setState({ ...state, loading: true }) try { Parse.serverURL = localStorage.getItem("baseUrl"); Parse.initialize(localStorage.getItem("parseAppId")); @@ -925,6 +925,7 @@ function Login(props) { <Title title={"Login Page"} /> {state.loading && ( <div + aria-live="assertive" style={{ position: "fixed", width: "100vw", @@ -936,63 +937,66 @@ function Login(props) { }} > <div + role="status" style={{ position: "fixed", fontSize: "50px", color: "#3ac9d6", top: "50%", - left: "45%" + left: "50%", + transform: 'translate(-50%, -50%)' }} className="loader-37" - ></div> + > + <span className="sr-only">Loading...</span> + </div> </div> )} {props.isloginVisible && props.isloginVisible ? ( - <div> + <div aria-labelledby="loginHeading" role="region"> <div className="md:m-10 lg:m-16 md:p-4 lg:p-10 p-5 bg-[#ffffff] md:border-[1px] md:border-gray-400 "> <div className="w-[250px] h-[66px] inline-block"> {state.hideNav ? ( - <img src={image} width="100%" alt="" /> + <img src={image} width="100%" alt="The image displays the OpenSign logo with a stylized blue square with an open corner, accompanied by the tagline Seal the Deal, Openly." /> ) : ( - <img src={image} width="100%" alt="" /> + <img src={image} width="100%" alt="The image displays the OpenSign logo with a stylized blue square with an open corner, accompanied by the tagline Seal the Deal, Openly." /> )} </div> <div className="grid grid-cols-1 md:grid-cols-2 gap-x-2"> - <div className=""> + <div> <div> - <form onSubmit={handleSubmit}> - <h2 className="text-[30px] mt-6">Welcome Back !</h2> - <span className="text-[12px] text-[#878787]"> - Login to your account - </span> - <div className="shadow-md rounded my-4"> + <form onSubmit={handleSubmit} aria-label="Login Form"> + <h1 className="text-[30px] mt-6">Welcome Back!</h1> + <fieldset className="outline outline-1 outline-slate-300/50 my-4"> + <legend className="text-xs font-bold px-6 mt-4">Login to your account</legend> <div className="px-6 py-4"> - <label className="block text-xs">Username</label> + <label className="block text-xs" htmlFor="email">Username</label> <input + id="email" type="text" - className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" + className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs" name="email" value={state.email} onChange={handleChange} required /> <hr className="my-2 border-none" /> - <label className="block text-xs">Password</label> + <label className="block text-xs" htmlFor="password">Password</label> <div className="relative"> <input + id="password" type={state.passwordVisible ? "text" : "password"} - className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" + className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs" name="password" value={state.password} onChange={handleChange} required /> <span - className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer ${ - state.passwordVisible - ? "text-[#007bff]" - : "text-black" - }`} + className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer ${state.passwordVisible + ? "text-[#007bff]" + : "text-black" + }`} onClick={togglePasswordVisibility} > {state.passwordVisible ? ( @@ -1003,32 +1007,32 @@ function Login(props) { </span> </div> </div> - </div> - <div className="flex flex-row justify-between items-center text-xs px-4 py-2"> + </fieldset> + <div className="flex flex-row justify-between items-center px-4 py-2"> <div> - <label className="form-check-label"> + <label className="form-check-label inline-block cursor-pointer" htmlFor="rememberpassword"> <input type="checkbox" - className="form-check-input" + className="form-check-input mr-2" value="" + id="rememberpassword" /> - <span className="text-center pl-1"> - Remember Password - </span> + <span className="text-[13px] pl-1">Remember Password</span> </label> </div> <div> - <span className="text-[13px]"> - <NavLink to={`/forgetpassword`}> - Forgot Password ? - </NavLink> - </span> + <NavLink + to="/forgetpassword" + className="text-[13px] underline focus:outline-none focus:ring-2 focus:ring-blue-600" + > + Forgot Password? + </NavLink> </div> </div> <div className="flex flex-col md:flex-row justify-between items-stretch gap-8 text-center text-xs font-bold mt-2"> <button type="submit" - className="rounded-sm bg-[#3ac9d6] text-white w-full py-3 shadow uppercase" + className="rounded-sm bg-[#3ac9d6] text-white w-full py-3 shadow outline-none uppercase focus:ring-2 focus:ring-blue-600" disabled={state.loading} > {state.loading ? "Loading..." : "Login"} @@ -1093,7 +1097,7 @@ function Login(props) { }} > {props.appInfo.googleClietId && - props.appInfo.googleClietId !== "" ? ( + props.appInfo.googleClietId !== "" ? ( <GoogleSignInBtn GoogleCred={props.appInfo.googleClietId} thirdpartyLoginfn={thirdpartyLoginfn} @@ -1107,14 +1111,18 @@ function Login(props) { {!state.hideNav && ( <div className="self-center"> <div className="mx-auto md:w-[300px] lg:w-[500px]"> - <img src={login_img} alt="bisec" width="100%" /> + <img src={login_img} alt="The image illustrates a person from behind, seated at a desk with a four-monitor computer setup, in an environment with a light blue and white color scheme, featuring a potted plant to the right." width="100%" /> </div> </div> )} </div> </div> - <div id="snackbar" style={{ backgroundColor: state.toastColor }}> + <div + id="snackbar" + role="alert" + style={{ backgroundColor: state.toastColor }} + > {state.toastDescription} </div> </div> diff --git a/apps/OpenSign/src/routes/PlanSubscriptions.js b/apps/OpenSign/src/routes/PlanSubscriptions.js index 61784986e..740277932 100644 --- a/apps/OpenSign/src/routes/PlanSubscriptions.js +++ b/apps/OpenSign/src/routes/PlanSubscriptions.js @@ -64,7 +64,7 @@ const PlanSubscriptions = () => { style={{ backgroundColor: "white", overflowY: "auto", - maxHeight: "600px", + maxHeight: "100%", "--theme-color": "#7952b3", "--plan-width": 30 }} diff --git a/apps/OpenSignServer/cloud/parsefunction/pdf/SignPDF.min.cjs b/apps/OpenSignServer/cloud/parsefunction/pdf/SignPDF.min.cjs index 509e2dd53..b84ba4c9b 100644 --- a/apps/OpenSignServer/cloud/parsefunction/pdf/SignPDF.min.cjs +++ b/apps/OpenSignServer/cloud/parsefunction/pdf/SignPDF.min.cjs @@ -1 +1 @@ -const signer=require("node-signpdf").default;class SignPDF{constructor(e,t){this.pdfDoc=e,this.certificate=t}async signPDF(){return signer.sign(this.pdfDoc,this.certificate,{passphrase:"emudhra"})}static unit8ToBuffer(e){var t=Buffer.alloc(e.byteLength),r=new Uint8Array(e);for(let e=0;e<t.length;++e)t[e]=r[e];return t}}module.exports=SignPDF; \ No newline at end of file +const signer=require("node-signpdf").default;class SignPDF{constructor(e,t){this.pdfDoc=e,this.certificate=t}async signPDF(){return signer.sign(this.pdfDoc,this.certificate, process.env.PASS_PHRASE ? {passphrase : process.env.PASS_PHRASE}: null)}static unit8ToBuffer(e){var t=Buffer.alloc(e.byteLength),r=new Uint8Array(e);for(let e=0;e<t.length;++e)t[e]=r[e];return t}}module.exports=SignPDF; \ No newline at end of file diff --git a/microfrontends/SignDocuments/src/Component/PageNotFound.js b/microfrontends/SignDocuments/src/Component/PageNotFound.js new file mode 100644 index 000000000..b9e912e32 --- /dev/null +++ b/microfrontends/SignDocuments/src/Component/PageNotFound.js @@ -0,0 +1,16 @@ +import React from "react"; + +const PageNotFound = () => { + return ( + <div className="flex items-center justify-center h-screen w-full bg-white rounded"> + <div className="text-center"> + <h1 className="text-[60px] lg:text-[120px] font-semibold text-black"> + 404 + </h1> + <p className="text-[30px] lg:text-[50px] text-black">Page Not Found</p> + </div> + </div> + ); +}; + +export default PageNotFound; diff --git a/microfrontends/SignDocuments/src/Component/component/emailComponent.js b/microfrontends/SignDocuments/src/Component/component/emailComponent.js index bdbb1eb52..5892c8ef5 100644 --- a/microfrontends/SignDocuments/src/Component/component/emailComponent.js +++ b/microfrontends/SignDocuments/src/Component/component/emailComponent.js @@ -109,8 +109,17 @@ function EmailComponent({ const handleToPrint = async (event) => { event.preventDefault(); - const base64 = await getBase64FromUrl(pdfUrl); - printModule({ printable: base64, type: "pdf", base64: true }); + const pdf = await getBase64FromUrl(pdfUrl); + const isAndroidDevice = navigator.userAgent.match(/Android/i); + const isAppleDevice = (/iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) && !window.MSStream + if (isAndroidDevice || isAppleDevice) { + const byteArray = Uint8Array.from(atob(pdf).split('').map(char => char.charCodeAt(0))); + const blob = new Blob([byteArray], { type: 'application/pdf' }); + const blobUrl = URL.createObjectURL(blob); + window.open(blobUrl, '_blank'); + } else { + printModule({ printable: pdf, type: "pdf", base64: true }); + } }; diff --git a/microfrontends/SignDocuments/src/Component/component/header.js b/microfrontends/SignDocuments/src/Component/component/header.js index bccdd46f2..8ab8d6994 100644 --- a/microfrontends/SignDocuments/src/Component/component/header.js +++ b/microfrontends/SignDocuments/src/Component/component/header.js @@ -60,8 +60,16 @@ function Header({ event.preventDefault(); const pdf = await getBase64FromUrl(pdfUrl); - - printModule({ printable: pdf, type: "pdf", base64: true }); + const isAndroidDevice = navigator.userAgent.match(/Android/i); + const isAppleDevice = (/iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) && !window.MSStream + if (isAndroidDevice || isAppleDevice) { + const byteArray = Uint8Array.from(atob(pdf).split('').map(char => char.charCodeAt(0))); + const blob = new Blob([byteArray], { type: 'application/pdf' }); + const blobUrl = URL.createObjectURL(blob); + window.open(blobUrl, '_blank'); + } else { + printModule({ printable: pdf, type: "pdf", base64: true }); + } }; //handle download signed pdf diff --git a/microfrontends/SignDocuments/src/Component/login.js b/microfrontends/SignDocuments/src/Component/login.js index 9995e1970..1636beff7 100644 --- a/microfrontends/SignDocuments/src/Component/login.js +++ b/microfrontends/SignDocuments/src/Component/login.js @@ -116,7 +116,8 @@ function Login() { localStorage.setItem("accesstoken", _user.sessionToken); setLoading(false); //navigate user to on signature page - navigate(`/recipientSignPdf/${id}/${contactBookId}`); + // navigate(`/recipientSignPdf/${id}/${contactBookId}`); + navigate(`/loadmf/signmicroapp/recipientSignPdf/${id}/${contactBookId}`); } } catch (error) {} } else { diff --git a/microfrontends/SignDocuments/src/Component/placeHolderSign.js b/microfrontends/SignDocuments/src/Component/placeHolderSign.js index 80ef92e27..39db3656b 100644 --- a/microfrontends/SignDocuments/src/Component/placeHolderSign.js +++ b/microfrontends/SignDocuments/src/Component/placeHolderSign.js @@ -166,7 +166,7 @@ function PlaceHolderSign() { getDocumentDetails(); } }, []); - + //function for get document details const getDocumentDetails = async () => { await axios @@ -275,7 +275,7 @@ function PlaceHolderSign() { getSignerPos(item, monitor); } }; - + const getSignerPos = (item, monitor) => { const key = Math.floor(1000 + Math.random() * 9000); let filterSignerPos = signerPos.filter( @@ -396,7 +396,7 @@ function PlaceHolderSign() { blockColor: color[isSelectListId], placeHolder: xyPosArr }; - + setSignerPos((prev) => [...prev, placeHolderPos]); } }; @@ -626,7 +626,7 @@ function PlaceHolderSign() { setSignBtnPosition([]); setPageNumber((prevPageNumber) => prevPageNumber + offset); } - + //function for capture position on hover signature button const handleDivClick = (e) => { const divRect = e.currentTarget.getBoundingClientRect(); @@ -679,7 +679,7 @@ function PlaceHolderSign() { year: "numeric" }); let sender = signersdata.ExtUserPtr.Email; - const signerMail = signersdata.Signers; + const signerMail = signersdata.Signers; for (let i = 0; i < signerMail.length; i++) { try { @@ -698,7 +698,8 @@ function PlaceHolderSign() { "parseAppId" )}&${localStorage.getItem("_appName")}`; - let signPdf = `https://qik-ai-org.github.io/Sign-MicroappV2/#/login/${signersdata.objectId}/${signerMail[i].Email}/${objectId}/${serverParams}`; + const hostUrl = window.location.origin + "/loadmf/signmicroapp"; + let signPdf = `${hostUrl}/login/${signersdata.objectId}/${signerMail[i].Email}/${objectId}/${serverParams}`; const themeBGcolor = themeColor(); let params = { diff --git a/microfrontends/SignDocuments/src/Routes.js b/microfrontends/SignDocuments/src/Routes.js index e9883138b..605212871 100644 --- a/microfrontends/SignDocuments/src/Routes.js +++ b/microfrontends/SignDocuments/src/Routes.js @@ -8,6 +8,7 @@ import Login from "./Component/login"; import DraftDocument from "./Component/DraftDocument"; import PdfRequestFiles from "./Component/PdfRequestFiles"; import LegaDrive from "./Component/LegaDrive/LegaDrive"; +import PageNotFound from "./Component/PageNotFound"; // `AppRoutes` is used to define route path of app and // it expose to host app, check moduleFederation.config.js for more @@ -33,7 +34,10 @@ function AppRoutes() { {/*Add default signature of user route */} <Route path="/managesign" element={<ManageSign />} /> {/* login page route */} - <Route path="/login/:id/:userMail/:contactBookId/:serverUrl" element={<Login />} /> + <Route + path="/login/:id/:userMail/:contactBookId/:serverUrl" + element={<Login />} + /> {/* draft document route to handle and navigate route page accordiing to document status */} <Route path="/draftDocument" element={<DraftDocument />} /> {/* for user signature (need your sign route) with row level data */} @@ -42,6 +46,8 @@ function AppRoutes() { <Route path="/pdfRequestFiles/:docId" element={<PdfRequestFiles />} /> {/* lega drive route */} <Route path="/legadrive" element={<LegaDrive />} /> + {/* Page Not Found */} + <Route path="*" element={<PageNotFound />} /> </Routes> </div> );