Merge pull request #11 from OpenSignLabs/staging

update
This commit is contained in:
Raktima
2023-11-06 14:48:05 +05:30
committed by GitHub
12 changed files with 309 additions and 156 deletions
+9
View File
@@ -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"
]
}
]
}
+3 -2
View File
@@ -2,7 +2,7 @@
<div align="center">
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 |
<a href="https://www.linkedin.com/company/opensign%E2%84%A2/about/">LinkedIn</a>
## An Open-Source Alternative to DocuSign, PandaDoc, SignNow, Adobe Sign, Smartwaiver, SignRequest, and HelloSign
## An open-source document e-signing solution
---
</div>
@@ -124,6 +124,7 @@ We would like to thank all our contributors and users for their support and feed
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andrew-opensignlabs"><img src="https://avatars.githubusercontent.com/u/148278535?v=4?s=100" width="100px;" alt="Andrew"/><br /><sub><b>Andrew</b></sub></a><br /><a href="#code-andrew-opensignlabs" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rishabjasrotia"><img src="https://avatars.githubusercontent.com/u/33950743?v=4?s=100" width="100px;" alt="Rishab"/><br /><sub><b>Rishab</b></sub></a><br /><a href="#code-rishabjasrotia" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://session.it"><img src="https://avatars.githubusercontent.com/u/327285?v=4?s=100" width="100px;" alt="Maurizio Pillitu"/><br /><sub><b>Maurizio Pillitu</b></sub></a><br /><a href="#bug-maoo" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://luisparra.dev"><img src="https://avatars.githubusercontent.com/u/16653744?v=4?s=100" width="100px;" alt="Luis Parra"/><br /><sub><b>Luis Parra</b></sub></a><br /><a href="#a11y-lsprr" title="Accessibility">️♿️</a></td>
</tr>
</tbody>
</table>
@@ -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) => {
<a
href={props.formData}
title={props.formData}
style={{ paddingBottom: "10px", color: "blue"}}
style={{ paddingBottom: "10px", color: "blue" }}
>
Download
</a>
@@ -0,0 +1,21 @@
import React from "react";
import { NavLink } from "react-router-dom";
const Menu = ({ item, isOpen, closeSidebar }) => {
return (
<li key={item.title} role="none">
<NavLink
to={`/${item.pageType}/${item.objectId}`}
className="mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4"
onClick={closeSidebar}
tabIndex={isOpen ? 0 : -1}
role="menuitem"
>
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
<span className="ml-3 lg:ml-4">{item.title}</span>
</NavLink>
</li>
)
}
export default Menu;
+34 -72
View File
@@ -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 (
<div
className={`${
isOpen ? "block" : "hidden"
} bg-white text-[#444] h-screen w-full md:w-[300px] overflow-y-auto transform transition-transform z-40 ${
isOpen ? "translate-x-0" : "-translate-x-full"
}`}
<aside
className={`${isOpen ? "block" : "hidden"
} bg-white text-[#444] h-screen w-full md:w-[300px] overflow-y-auto transform transition-transform z-40 ${isOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
<div className="flex px-2 py-3 gap-2 items-center shadow-md">
<div className="w-[75px] h-[75px] rounded-full ring-[2px] ring-offset-2 ring-gray-400 overflow-hidden">
<img className="w-full h-full object-contain" src={image} alt="img" />
<img className="w-full h-full object-cover" src={image} alt="Profile" />
</div>
<div>
<p className="text-[14px] font-bold">{username && username}</p>
<p
className={`text-[12px] ${tenantname && tenantname ? "mt-2" : ""}`}
>
{tenantname && tenantname}
<p className="text-[14px] font-bold">{username}</p>
<p className={`text-[12px] ${tenantname ? "mt-2" : ""}`}>
{tenantname}
</p>
</div>
</div>
<ul className="text-sm">
{menuList.length > 0 && (
<>
{menuList.map((item) =>
!item.children ? (
<li key={item.title} onClick={() => closeSidebar()}>
<Link
className={`mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 hover:no-underline cursor-pointer`}
to={`/${item.pageType}/${item.objectId}`}
>
<i className={item.icon + " text-[18px]"}></i>
<span title={item.description} className="ml-3 lg:ml-4">
{item.title}
</span>
</Link>
</li>
) : (
<Submenu key={item.title} icon={item.icon} title={item.title}>
{item.children.map((item) => (
<li key={item.title} onClick={() => closeSidebar()} className="pl-6 md:pl-8 hover:bg-[#eef1f5] cursor-pointer">
<Link
className={`mx-auto flex items-center p-2 lg:p-3 hover:no-underline`}
to={`/${item.pageType}/${item.objectId}`}
>
<i className={item.icon + " text-[18px]"}></i>
<span title={item.description} className="ml-3 lg:ml-4">
{item.title}
</span>
</Link>
</li>
))}
</Submenu>
)
)}
</>
)}
</ul>
<div className="mt-4 flex justify-center items-center text-[25px] text-black gap-3">
<NavLink to="https://github.com/opensignlabs/opensign" target="_blank">
<i className="fa-brands fa-github"></i>
</NavLink>
<NavLink
to="https://www.linkedin.com/company/opensign%E2%84%A2/"
target="_blank"
>
<i className="fa-brands fa-linkedin"></i>
</NavLink>
<NavLink to="https://www.twitter.com/opensignlabs" target="_blank">
<i className="fa-brands fa-square-x-twitter"></i>
</NavLink>
<NavLink to="https://discord.com/invite/xe9TDuyAyj" target="_blank">
<i className="fa-brands fa-discord"></i>
</NavLink>
</div>
</div>
<nav aria-label="OpenSign Sidebar Navigation">
<ul className="text-sm" role="menubar" aria-label="OpenSign Sidebar Navigation">
{menuList.map((item) => (
!item.children ? (
<Menu key={item.title} item={item} isOpen={isOpen} closeSidebar={closeSidebar} />
) : (
<Submenu key={item.title} item={item} closeSidebar={closeSidebar} />
)
))}
</ul>
</nav>
<footer className="mt-4 flex justify-center items-center text-[25px] text-black gap-3">
<SocialMedia />
</footer>
</aside>
);
};
@@ -0,0 +1,27 @@
import React from "react";
import { NavLink } from "react-router-dom";
const SocialMedia = () => {
return (
<React.Fragment>
<NavLink to="https://github.com/opensignlabs/opensign" target="_blank" rel="noopener noreferrer">
<i aria-hidden="true" className="fa-brands fa-github"></i>
<span className="fa-sr-only">OpenSign&apos;s Github</span>
</NavLink>
<NavLink to="https://www.linkedin.com/company/opensign%E2%84%A2/" target="_blank" rel="noopener noreferrer">
<i aria-hidden="true" className="fa-brands fa-linkedin"></i>
<span className="fa-sr-only">OpenSign&apos;s LinkedIn</span>
</NavLink>
<NavLink to="https://www.twitter.com/opensignlabs" target="_blank" rel="noopener noreferrer">
<i aria-hidden="true" className="fa-brands fa-square-x-twitter"></i>
<span className="fa-sr-only">OpenSign&apos;s Twitter</span>
</NavLink>
<NavLink to="https://discord.com/invite/xe9TDuyAyj" target="_blank" rel="noopener noreferrer">
<i aria-hidden="true" className="fa-brands fa-discord"></i>
<span className="fa-sr-only">OpenSign&apos;s Discord</span>
</NavLink>
</React.Fragment>
)
}
export default SocialMedia;
+33 -11
View File
@@ -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 (
<div>
<li role="none">
<button
onClick={toggleSubmenu}
className="w-full mx-auto flex items-center text-left text-[#444] p-3 lg:p-4 hover:bg-[#eef1f5] focus:outline-none"
className="w-full flex items-center text-left text-[#444] p-3 lg:p-4 hover:bg-[#eef1f5] focus:ring-2 focus:ring-blue-600"
aria-expanded={submenuOpen}
aria-haspopup="true"
aria-controls={`submenu-${title}`}
>
<i className={icon + " text-[18px]"}></i>
<i className={`${icon} text-[18px]`}></i>
<div className="flex justify-between items-center w-full">
<span className="ml-3 lg:ml-4">{title}</span>
<i
className={
isOpen ? "fa-solid fa-angle-down" : "fa-solid fa-angle-right"
}
className={`${submenuOpen ? "fa-solid fa-angle-down" : "fa-solid fa-angle-right"
}`}
aria-hidden="true"
></i>
</div>
</button>
{isOpen && <ul>{children}</ul>}
</div>
{submenuOpen && (
<ul id={`submenu-${title}`} role="menu" aria-label={`${title} submenu`}>
{children.map((childItem) => (
<li key={childItem.title} role="none">
<NavLink
to={`/${childItem.pageType}/${childItem.objectId}`}
className="block pl-6 md:pl-8 py-2 text-sm text-gray-700 hover:bg-blue-500 hover:text-white"
onClick={closeSidebar}
role="menuitem"
tabIndex={submenuOpen ? 0 : -1}
>
<i className={`${childItem.icon} text-[18px]`} aria-hidden="true"></i>
<span className="ml-3 lg:ml-4">{childItem.title}</span>
</NavLink>
</li>
))}
</ul>
)}
</li>
);
};
@@ -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 (
<div className="p-2 overflow-x-scroll w-full bg-white rounded-md">
<h2 className="text-[23px] font-light my-2">{ReportName}</h2>
@@ -60,7 +92,7 @@ const ReportTable = ({ ReportName, List, actions }) => {
<tbody className="text-[12px]">
{List?.length > 0 ? (
<>
{List.map((item, index) => (
{currentLists.map((item, index) => (
<tr className="border-t-[1px]" key={index}>
<td className="px-4 py-2">{index + 1}</td>
<td className="px-4 py-2 font-semibold">{item?.Name} </td>
@@ -121,8 +153,46 @@ const ReportTable = ({ ReportName, List, actions }) => {
)}
</tbody>
</table>
<div className="flex flex-wrap items-center gap-2 p-2 border-t-[1px]">
{List.length > 10 && (
<>
{currentPage > 1 && (
<button
onClick={() => paginateBack()}
className="bg-blue-400 text-white rounded px-3 py-2 focus:outline-none"
>
Prev
</button>
)}
</>
)}
{pageNumbers.map((x) => (
<button
key={x}
onClick={() => setCurrentPage(x)}
className=" bg-sky-400 text-white rounded px-3 py-2 focus:outline-none"
>
{x}
</button>
))}
{isMoreDocs && (
<button className="text-black rounded px-1 py-2">...</button>
)}
{List.length > 10 && (
<>
{pageNumbers.includes(currentPage + 1) && (
<button
onClick={() => paginateFront()}
className="bg-blue-400 text-white rounded px-3 py-2 focus:outline-none"
>
Next
</button>
)}
</>
)}
</div>
{List?.length <= 0 && (
<div className="flex flex-col items-center justify-center w-full bg-white roundedm py-4">
<div className="flex flex-col items-center justify-center w-full bg-white rounded py-4">
<div className="w-[60px] h-[60px] overflow-hidden">
<img className="w-full h-full object-contain" src={pad} alt="img" />
</div>
+57 -8
View File
@@ -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}
/>
) : (
<div className="flex items-center justify-center h-screen w-full bg-white rounded">
+1 -1
View File
@@ -251,7 +251,7 @@ const Signup = (props) => {
element.pageType
);
setState({ loading: false });
navigate("/login");
navigate("/");
} else {
extendedInfo.forEach((x) => {
if (x.TenantId) {
+1
View File
@@ -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',
+2 -1
View File
@@ -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"
},