diff --git a/.all-contributorsrc b/.all-contributorsrc index f1976eec6..202612d32 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -169,6 +169,15 @@ "contributions": [ "bug" ] + }, + { + "login": "lsprr", + "name": "Luis Parra", + "avatar_url": "https://avatars.githubusercontent.com/u/16653744?v=4", + "profile": "https://luisparra.dev", + "contributions": [ + "a11y" + ] } ] } diff --git a/README.md b/README.md index c2a7d86ea..200487ed5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-The free open source alternative to DocuSign | PandaDoc | SignNow | Adobe Sign | Smartwaiver | SignRequest | HelloSign | Zoho sign. +The free and open source alternative to DocuSign [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/opensignlabs/opensign.svg)](http://isitmaintained.com/project/opensignlabs/opensign "Average time to resolve an issue") [![All Contributors](https://img.shields.io/github/all-contributors/opensignlabs/opensign?color=ee8449&style=flat-square)](#contributors) @@ -23,7 +23,7 @@ The free open source alternative to DocuSign | PandaDoc | SignNow | Adobe Sign | LinkedIn -## An Open-Source Alternative to DocuSign, PandaDoc, SignNow, Adobe Sign, Smartwaiver, SignRequest, and HelloSign +## An open-source document e-signing solution ---
@@ -124,6 +124,7 @@ We would like to thank all our contributors and users for their support and feed Andrew
Andrew

💻 Rishab
Rishab

💻 Maurizio Pillitu
Maurizio Pillitu

🐛 + Luis Parra
Luis Parra

️️️️♿️ diff --git a/apps/OpenSign/src/components/fields/FileUpload.js b/apps/OpenSign/src/components/fields/FileUpload.js index 2b0840276..226640d44 100644 --- a/apps/OpenSign/src/components/fields/FileUpload.js +++ b/apps/OpenSign/src/components/fields/FileUpload.js @@ -1,8 +1,7 @@ import React, { useState, useEffect } from "react"; import { SaveFileSize } from "../../constant/saveFileSize"; -// import Parse from "parse"; -// import sanitizeFileName from "../../primitives/sanitizeFileName"; -import axios from "axios"; +import Parse from "parse"; +import sanitizeFileName from "../../primitives/sanitizeFileName"; const FileUpload = (props) => { const [parseBaseUrl] = useState(localStorage.getItem("baseUrl")); @@ -69,53 +68,44 @@ const FileUpload = (props) => { }; const handleFileUpload = async (file) => { + Parse.serverURL = parseBaseUrl; + Parse.initialize(parseAppId); setfileload(true); - const file_url = parseBaseUrl.slice(0, -4); - const url = `${file_url}file_upload`; - const formData = new FormData(); - formData.append("file", file); - const config = { - headers: { - "content-type": "multipart/form-data", - "X-Parse-Application-Id": parseAppId - }, - onUploadProgress: function (progressEvent) { - var percentCompleted = Math.round( - (progressEvent.loaded * 100) / progressEvent.total - ); - setpercentage(percentCompleted); - } - }; + const size = file.size; + const fileName = file.name; + const name = sanitizeFileName(fileName); + const pdfFile = file; + const parseFile = new Parse.File(name, pdfFile); try { - await axios - .post(url, formData, config) - .then((res) => { - if (res.data.status === "Error") { - alert(res.data.message); + const response = await parseFile.save({ + progress: (progressValue, loaded, total, { type }) => { + if (type === "upload" && progressValue !== null) { + const percentCompleted = Math.round((loaded * 100) / total); + // console.log("percentCompleted ", percentCompleted); + setpercentage(percentCompleted); } - setFileUpload(res.data.imageUrl); - props.onChange(res.data.imageUrl); - setfileload(false); - setpercentage(0); + } + }); - if (res.data.imageUrl) { - SaveFileSize(file.size, res.data.imageUrl); - return res.data.imageUrl; - } - }) - .catch((err) => { - alert(`${err.message}`); - setfileload(false); - setpercentage(0); - }); + setFileUpload(response.url()); + props.onChange(response.url()); + setfileload(false); + // The response object will contain information about the uploaded file + // console.log("File uploaded:", response); + + // You can access the URL of the uploaded file using response.url() + // console.log("File URL:", response.url()); + if (response.url()) { + SaveFileSize(size, response.url()); + return response.url(); + } } catch (error) { - alert(error.message); setfileload(false); setpercentage(0); + console.error("Error uploading file:", error); } }; - let fileView = props.formData && props.schema.uploadtype === "s3viajw" ? null : props.formData && @@ -124,7 +114,7 @@ const FileUpload = (props) => { Download diff --git a/apps/OpenSign/src/components/sidebar/Menu.js b/apps/OpenSign/src/components/sidebar/Menu.js new file mode 100644 index 000000000..459451b23 --- /dev/null +++ b/apps/OpenSign/src/components/sidebar/Menu.js @@ -0,0 +1,21 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; + +const Menu = ({ item, isOpen, closeSidebar }) => { + return ( +
  • + + + {item.title} + +
  • + ) +} + +export default Menu; \ No newline at end of file diff --git a/apps/OpenSign/src/components/sidebar/Sidebar.js b/apps/OpenSign/src/components/sidebar/Sidebar.js index 1d0ff7ed8..85ac268ec 100644 --- a/apps/OpenSign/src/components/sidebar/Sidebar.js +++ b/apps/OpenSign/src/components/sidebar/Sidebar.js @@ -1,115 +1,77 @@ import React, { useState, useEffect } from "react"; -import { Link, NavLink } from "react-router-dom"; + +import Menu from './Menu'; import Submenu from "./SubMenu"; -import dp from "../../assets/images/dp.png"; +import SocialMedia from './SocialMedia'; + import Parse from "parse"; +import dp from "../../assets/images/dp.png"; + const Sidebar = ({ isOpen, closeSidebar }) => { + const [menuList, setmenuList] = useState([]); let username = localStorage.getItem("username"); const image = localStorage.getItem("profileImg") || dp; const tenantname = localStorage.getItem("TenantName"); - const [menuList, setmenuList] = useState([]); useEffect(() => { if (localStorage.getItem("accesstoken")) { menuItem(); } }, []); + const menuItem = async () => { const parseBaseUrl = localStorage.getItem("baseUrl"); const parseAppId = localStorage.getItem("parseAppId"); + try { Parse.serverURL = parseBaseUrl; Parse.initialize(parseAppId); var sideMenu = Parse.Object.extend("w_menu"); var query = new Parse.Query(sideMenu); query.equalTo("objectId", localStorage.getItem("defaultmenuid")); - + const results = await query.first(); const resultjson = results.toJSON(); - // console.log("resultjson ", resultjson); let result = resultjson; setmenuList(result.menuItems); } catch (e) { console.error("Problem", e); } + }; return ( -
    - img + Profile
    -

    {username && username}

    -

    - {tenantname && tenantname} +

    {username}

    +

    + {tenantname}

    - -
    - - - - - - - - - - - - -
    -
    + + + ); }; diff --git a/apps/OpenSign/src/components/sidebar/SocialMedia.js b/apps/OpenSign/src/components/sidebar/SocialMedia.js new file mode 100644 index 000000000..813c47e15 --- /dev/null +++ b/apps/OpenSign/src/components/sidebar/SocialMedia.js @@ -0,0 +1,27 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; + +const SocialMedia = () => { + return ( + + + + OpenSign's Github + + + + OpenSign's LinkedIn + + + + OpenSign's Twitter + + + + OpenSign's Discord + + + ) +} + +export default SocialMedia; \ No newline at end of file diff --git a/apps/OpenSign/src/components/sidebar/SubMenu.js b/apps/OpenSign/src/components/sidebar/SubMenu.js index 6b3d59582..3b3adac42 100644 --- a/apps/OpenSign/src/components/sidebar/SubMenu.js +++ b/apps/OpenSign/src/components/sidebar/SubMenu.js @@ -1,30 +1,52 @@ import React, { useState } from "react"; +import { NavLink } from "react-router-dom"; -const Submenu = ({ icon, title, children }) => { - const [isOpen, setIsOpen] = useState(false); +const Submenu = ({ item, closeSidebar }) => { + const [submenuOpen, setSubmenuOpen] = useState(false); + const { title, icon, children } = item; const toggleSubmenu = () => { - setIsOpen(!isOpen); + setSubmenuOpen(!submenuOpen); }; return ( -
    +
  • - {isOpen && } -
  • + {submenuOpen && ( + + )} + ); }; diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 22cea983d..0bf111c1a 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -1,9 +1,37 @@ -import React from "react"; +import React, { useState, useEffect, useMemo } from "react"; import pad from "../assets/images/pad.svg"; import { useNavigate } from "react-router-dom"; -const ReportTable = ({ ReportName, List, actions }) => { +const ReportTable = ({ + ReportName, + List, + actions, + setIsNextRecord, + isMoreDocs +}) => { const navigate = useNavigate(); + const [currentPage, setCurrentPage] = useState(1); + const docPerPage = 10; + // const pageNumbers = []; + + // For loop is used to calculate page numbers visible below table + // Initialize pageNumbers using useMemo to avoid unnecessary re-creation + const pageNumbers = useMemo(() => { + const calculatedPageNumbers = []; + for (let i = 1; i <= Math.ceil(List.length / docPerPage); i++) { + calculatedPageNumbers.push(i); + } + return calculatedPageNumbers; + }, [List]); + // below useEffect reset currenpage to 1 if user change route + useEffect(() => { + return () => setCurrentPage(1); + }, []); + + // `formatRow` is used to show data in poper manner like + // if data is of array type then it will join array items with "," + // if data is of object type then it Name values will be show in row + // if no data available it will show hyphen "-" const formatRow = (row) => { if (Array.isArray(row)) { let updateArr = row.map((x) => x.Name); @@ -14,24 +42,17 @@ const ReportTable = ({ ReportName, List, actions }) => { return "-"; } }; + // below useEffect is used to render next record if IsMoreDoc is true + // second last value of pageNumber array is same as currentPage + useEffect(() => { + if (isMoreDocs && pageNumbers[pageNumbers.length - 1] === currentPage) { + setIsNextRecord(true); + } + }, [isMoreDocs, pageNumbers, currentPage, setIsNextRecord]); + + // `handlemicroapp` is used to open microapp const handlemicroapp = (item, url) => { localStorage.removeItem("rowlevel"); - // const params = new URLSearchParams(url); - // let arr = []; - // for (let [key, value] of params.entries()) { - // if (key === "remoteUrl") { - // arr.push( - // `${key}=${window.btoa( - // window.location.origin + "/mfbuild/remoteEntry.js" - // )}` - // ); - // } else { - // arr.push(`${key}=${value}`); - // } - // } - // const remoteUrl = arr.join("&"); - // console.log("arr", remoteUrl); - navigate("/rpmf/" + url); localStorage.setItem("rowlevel", JSON.stringify(item)); // localStorage.setItem("rowlevelMicro"); @@ -39,6 +60,17 @@ const ReportTable = ({ ReportName, List, actions }) => { const handlebtn = () => { console.log("clicked"); }; + + // Get current list + const indexOfLastDoc = currentPage * docPerPage; + const indexOfFirstDoc = indexOfLastDoc - docPerPage; + // `currentLists` is total record render on current page + const currentLists = List.slice(indexOfFirstDoc, indexOfLastDoc); + + // Change page + const paginateFront = () => setCurrentPage(currentPage + 1); + const paginateBack = () => setCurrentPage(currentPage - 1); + return (

    {ReportName}

    @@ -60,7 +92,7 @@ const ReportTable = ({ ReportName, List, actions }) => { {List?.length > 0 ? ( <> - {List.map((item, index) => ( + {currentLists.map((item, index) => ( {index + 1} {item?.Name} @@ -121,8 +153,46 @@ const ReportTable = ({ ReportName, List, actions }) => { )} +
    + {List.length > 10 && ( + <> + {currentPage > 1 && ( + + )} + + )} + {pageNumbers.map((x) => ( + + ))} + {isMoreDocs && ( + + )} + {List.length > 10 && ( + <> + {pageNumbers.includes(currentPage + 1) && ( + + )} + + )} +
    {List?.length <= 0 && ( -
    +
    img
    diff --git a/apps/OpenSign/src/routes/Report.js b/apps/OpenSign/src/routes/Report.js index d82dc3630..efec224ac 100644 --- a/apps/OpenSign/src/routes/Report.js +++ b/apps/OpenSign/src/routes/Report.js @@ -12,14 +12,37 @@ const Report = () => { const [isLoader, setIsLoader] = useState(true); const [reportName, setReportName] = useState(""); const [actions, setActions] = useState([]); + const [isNextRecord, setIsNextRecord] = useState(false); + const [isMoreDocs, setIsMoreDocs] = useState(true); + const abortController = new AbortController(); + + // below useEffect is call when id param change useEffect(() => { setReportName(""); + setList([]); getReportData(); + + // Function returned from useEffect is called on unmount + return () => { + setIsLoader(true); + setList([]); + setIsNextRecord(false); + // Here it'll abort the fetch + abortController.abort(); + }; // eslint-disable-next-line }, [id]); - const getReportData = async () => { - setIsLoader(true); + // below useEffect call when isNextRecord state is true and fetch next record + useEffect(() => { + if (isNextRecord) { + getReportData(List.length, 200); + } + // eslint-disable-next-line + }, [isNextRecord]); + + const getReportData = async (skipUserRecord = 0, limit = 200) => { + // setIsLoader(true); const json = reportJson(id); if (json) { setActions(json.actions); @@ -39,8 +62,11 @@ const Report = () => { "X-Parse-Session-Token": localStorage.getItem("accesstoken") }; try { - const url = `${serverURL}?where=${strParams}&keys=${strKeys}&order=${orderBy}&include=AuditTrail.UserPtr`; - const res = await axios.get(url, { headers: headers }); + const url = `${serverURL}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skipUserRecord}&limit=${limit}&include=AuditTrail.UserPtr`; + const res = await axios.get(url, { + headers: headers, + signal: abortController.signal // is used to cancel fetch query + }); if (id === "4Hhwbp482K") { const listData = res.data?.results.filter( (x) => x.Signers.length > 0 @@ -66,14 +92,35 @@ const Report = () => { } } } - setList(arr); + if (arr.length === 10) { + setIsMoreDocs(true); + } else { + setIsMoreDocs(false); + } + setList((prevRecord) => + prevRecord.length > 0 ? [...prevRecord, ...arr] : arr + ); } else { - setList(res.data?.results); + if (res.data.results.length === 10) { + setIsMoreDocs(true); + console.log("here"); + } else { + setIsMoreDocs(false); + } + setIsNextRecord(false); + setList((prevRecord) => + prevRecord.length > 0 + ? [...prevRecord, ...res.data.results] + : res.data.results + ); } setIsLoader(false); } catch (err) { - console.log("err ", err); - setIsLoader(false); + const isCancel = axios.isCancel(err); + if (!isCancel) { + console.log("err ", err); + setIsLoader(false); + } } } else { setIsLoader(false); @@ -106,6 +153,8 @@ const Report = () => { ReportName={reportName} List={List} actions={actions} + setIsNextRecord={setIsNextRecord} + isMoreDocs={isMoreDocs} /> ) : (
    diff --git a/apps/OpenSign/src/routes/Signup.js b/apps/OpenSign/src/routes/Signup.js index ea3481cb7..6378e6785 100644 --- a/apps/OpenSign/src/routes/Signup.js +++ b/apps/OpenSign/src/routes/Signup.js @@ -251,7 +251,7 @@ const Signup = (props) => { element.pageType ); setState({ loading: false }); - navigate("/login"); + navigate("/"); } else { extendedInfo.forEach((x) => { if (x.TenantId) { diff --git a/apps/OpenSignServer/index.js b/apps/OpenSignServer/index.js index f0d494c44..814d9cf58 100644 --- a/apps/OpenSignServer/index.js +++ b/apps/OpenSignServer/index.js @@ -63,6 +63,7 @@ export const config = { publicServerURL: process.env.SERVER_URL || 'http://localhost:8080/app', // Your apps name. This will appear in the subject and body of the emails that are sent. appName: 'Open Sign', + allowClientClassCreation: false, emailAdapter: process.env.MAILGUN_API_KEY ? { module: 'parse-server-api-mail-adapter', diff --git a/microfrontends/SignDocuments/package.json b/microfrontends/SignDocuments/package.json index 3c8e28903..97970e988 100644 --- a/microfrontends/SignDocuments/package.json +++ b/microfrontends/SignDocuments/package.json @@ -2,6 +2,7 @@ "name": "signmicroapp", "version": "0.1.0", "private": true, + "homepage": "./mfbuild", "dependencies": { "@radix-ui/themes": "^1.1.2", "@react-pdf/renderer": "^3.1.9", @@ -35,7 +36,7 @@ }, "scripts": { "start": "mf-cra start", - "build": "CI=false && BUILD_PATH='./mfbuild' mf-cra build && mv mfbuild ../../apps/OpenSign/public", + "build": "CI=false && PUBLIC_URL=./mfbuild BUILD_PATH='./mfbuild' mf-cra build && mv mfbuild ../../apps/OpenSign/public", "test": "react-scripts test", "eject": "react-scripts eject" },