first commit
This commit is contained in:
32
src/Components/Accessibility.js
Normal file
32
src/Components/Accessibility.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const Accessibility = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tos-container">
|
||||
<h1>Accessibility</h1>
|
||||
<p className="tos-para">
|
||||
Novodraft is committed to designing products and services to pursuant
|
||||
to the requirements of Section 508 of the Rehabilitation Act of 1973
|
||||
(Electronic and Information Technology Accessibility Standards, 36
|
||||
C.F.R. § 1194), and other applicable accessibility standards, to the
|
||||
maximum degree practicable.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
NovoDraft strives to develop and provide legal artificial intelligence
|
||||
information technology products and services that are accessible and
|
||||
usable by all people, including those with disabilities and special
|
||||
needs.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
To that end, if you find any of our products or services could better
|
||||
meet your special needs, please do not hesitate to contact us.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Accessibility;
|
||||
186
src/Components/Account/AccountPage.js
Normal file
186
src/Components/Account/AccountPage.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import { useState, useContext, useEffect } from "react";
|
||||
import { db } from "../../firebase";
|
||||
import { collection, onSnapshot, query, where } from "firebase/firestore";
|
||||
import Button from "../../pageElements/Button";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext";
|
||||
import { AuthContext } from "../../Context/AuthProvider";
|
||||
import ProgressBar from "../../pageElements/ProgressBar";
|
||||
|
||||
const AccountPage = () => {
|
||||
const { currentUserCreatedAt } = useContext(AuthContext);
|
||||
const { appState } = useContext(AppContext);
|
||||
const { group } = appState;
|
||||
const [docsGenerated, setDocsGenerated] = useState(null);
|
||||
const [docsAllowed, setDocsAllowed] = useState(200);
|
||||
const appUserId = group ? group.appUserId : null;
|
||||
|
||||
function getDocumentsCount() {
|
||||
if (!appUserId) {
|
||||
return;
|
||||
}
|
||||
return onSnapshot(
|
||||
query(collection(db, "documents"), where("ownerId", "==", appUserId)),
|
||||
(snapshot) => setDocsGenerated(snapshot.docs.length)
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(getDocumentsCount, [appUserId]);
|
||||
|
||||
const telNumberFormat = (num) => {
|
||||
return `(${num.slice(0, 3)}) ${num.slice(4, 7)}-${num.slice(6, 10)}`;
|
||||
};
|
||||
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="account-container">
|
||||
<div className="account-left-column">
|
||||
<div className="account-menu">
|
||||
<div className="account-menu-item">
|
||||
<a href="#">Personal Settings</a>
|
||||
</div>
|
||||
<div className="account-menu-item">
|
||||
<a href="#">Payment Settings</a>
|
||||
</div>
|
||||
<div className="account-menu-item">
|
||||
<a href="#">Membership Settings</a>
|
||||
</div>
|
||||
<div className="account-menu-item">
|
||||
<a href="#">Order History</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="account-column">
|
||||
{docsGenerated !== null ? (
|
||||
<div className="account-card account-docs-generated">
|
||||
<div>
|
||||
You have generated {docsGenerated} out of {docsAllowed} response
|
||||
documents this billing period
|
||||
</div>
|
||||
<div className="account-card-title">Upgrade to get more</div>
|
||||
<div className="account-docs-progress-bar">
|
||||
<ProgressBar value={docsGenerated} total={docsAllowed} />
|
||||
</div>
|
||||
<div className="account-card-actions">
|
||||
<Button
|
||||
className="primary-button"
|
||||
labelText="Upgrade to add more"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="accound-section">
|
||||
Take full advantage of the features included in your account
|
||||
</div>
|
||||
<div className="account-group">
|
||||
<div className="account-card">
|
||||
<div className="account-card-title">Give the gift of Novodraft</div>
|
||||
<div className="account-card-description">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</div>
|
||||
<div className="account-card-actions">
|
||||
<Button className="primary-button" labelText="Upgrade" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="account-card">
|
||||
<div className="account-card-title">Give the gift of Novodraft</div>
|
||||
<div className="account-card-description">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</div>
|
||||
<div className="account-card-actions">
|
||||
<Button className="primary-button" labelText="Upgrade" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="account-card">
|
||||
<div className="account-card-title">Give the gift of Novodraft</div>
|
||||
<div className="account-card-description">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</div>
|
||||
<div className="account-card-actions">
|
||||
<Button className="primary-button" labelText="Upgrade" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="account-card">
|
||||
<div className="account-card-title">Give the gift of Novodraft</div>
|
||||
<div className="account-card-description">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</div>
|
||||
<div className="account-card-actions">
|
||||
<Button className="primary-button" labelText="Upgrade" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="account-card account-support">
|
||||
<div className="account-support-info">
|
||||
<div className="account-support-title account-support-info-title">
|
||||
Need Support?
|
||||
</div>
|
||||
<div>
|
||||
<a href="tel:+12223334444">1-222-333-4444</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="mailto:test@example.com">test@example.com</a>
|
||||
</div>
|
||||
<div>
|
||||
Or use our <a href="#">contact form</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="account-support-links">
|
||||
<div className="account-support-title account-support-links-title">
|
||||
FAQ
|
||||
</div>
|
||||
<div>
|
||||
<a href="#">Support</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#">Billing</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#">Privacy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="accound-section">Active Subscriptions</div>
|
||||
<div className="account-card account-subscriptions">
|
||||
<div className="account-subscriptions-title">
|
||||
30 day free trial - 1 allowed document
|
||||
</div>
|
||||
<div className="account-subscriptions-item">
|
||||
<div className="account-subscriptions-item-label">Amount:</div>
|
||||
<div className="account-subscriptions-item-value">Free!</div>
|
||||
</div>
|
||||
{typeof currentUserCreatedAt !== "undefined" ? (
|
||||
<div className="account-subscriptions-item">
|
||||
<div className="account-subscriptions-item-label">Activated:</div>
|
||||
<div className="account-subscriptions-item-value">
|
||||
{currentUserCreatedAt.toLocaleDateString("en-US")}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="account-subscriptions-actions">
|
||||
<Button className="primary-button" labelText="Cancel Account" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="account-column">
|
||||
<div className="account-card account-card-right-account account-info">
|
||||
<div className="account-info-name">
|
||||
{group.firstName} {group.lastName}
|
||||
</div>
|
||||
<div>
|
||||
<a href={`mailto:${group.email}`}>{group.email}</a>
|
||||
</div>
|
||||
<div>{telNumberFormat(group.telephone)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountPage;
|
||||
200
src/Components/Account/PasswordReset.js
Normal file
200
src/Components/Account/PasswordReset.js
Normal file
@@ -0,0 +1,200 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { checkActionCode, confirmPasswordReset, getAuth, sendPasswordResetEmail } from "firebase/auth";
|
||||
import Button from "../../pageElements/Button";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
const PasswordResetPage = () => {
|
||||
const auth = getAuth();
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [isCodeValid, setIsCodeValid] = useState(null);
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [done, setDone] = useState(false);
|
||||
const [notice, setNotice] = useState("");
|
||||
const [searchParams] = useSearchParams();
|
||||
const mode = searchParams.get("mode");
|
||||
const oobCode = searchParams.get("oobCode");
|
||||
|
||||
useEffect(() => {
|
||||
async function handleCheckCode() {
|
||||
if (!oobCode) {
|
||||
setIsCodeValid(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await checkActionCode(auth, oobCode);
|
||||
setIsCodeValid(true);
|
||||
} catch (error) {
|
||||
setIsCodeValid(false);
|
||||
}
|
||||
}
|
||||
handleCheckCode();
|
||||
}, [auth, oobCode]);
|
||||
|
||||
const handleResetPassword = async (e) => {
|
||||
e.preventDefault();
|
||||
if (isBusy) {
|
||||
return;
|
||||
}
|
||||
setIsBusy(true);
|
||||
setNotice("");
|
||||
try {
|
||||
await sendPasswordResetEmail(auth, email);
|
||||
setDone(true);
|
||||
} catch (error) {
|
||||
if (error.code === "auth/invalid-email") {
|
||||
setNotice("Invalid email.");
|
||||
} else {
|
||||
setNotice(`Error occured (${error.code}).`);
|
||||
}
|
||||
} finally {
|
||||
setIsBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmPasswordReset = async (e) => {
|
||||
e.preventDefault();
|
||||
if (isBusy) {
|
||||
return;
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
setNotice("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
setIsBusy(true);
|
||||
try {
|
||||
await confirmPasswordReset(auth, oobCode, password);
|
||||
setDone(true);
|
||||
} catch (error) {
|
||||
if (error.code === "auth/weak-password") {
|
||||
setNotice("Weak password.");
|
||||
} else {
|
||||
setNotice(`Error occured (${error.code}).`);
|
||||
}
|
||||
} finally {
|
||||
setIsBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
const resetMode = mode === "resetPassword" ? "resetPassword" : "enterEmail";
|
||||
return (
|
||||
<div className="password-reset-container">
|
||||
<div className="password-reset-form-wrapper">
|
||||
<div className="password-reset-form">
|
||||
{resetMode === "enterEmail" ? <>
|
||||
<div className="password-reset-header">
|
||||
<h2 className="password-reset-header-text">
|
||||
Enter Account Email
|
||||
</h2>
|
||||
</div>
|
||||
{!done ? (
|
||||
<form>
|
||||
<div className="form-floating">
|
||||
<input
|
||||
type="email"
|
||||
className="form-control"
|
||||
id="emailInput"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
disabled={isBusy}
|
||||
></input>
|
||||
<label htmlFor="emailInput" className="form-label">
|
||||
Email
|
||||
</label>
|
||||
</div>
|
||||
<div className="alert-box">
|
||||
{"" !== notice && (
|
||||
<div className="password-reset-alert" role="alert">
|
||||
{notice}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="password-reset-button-wrapper">
|
||||
<Button
|
||||
className="primary-button"
|
||||
onClick={(e) => handleResetPassword(e)}
|
||||
labelText="Submit"
|
||||
disabled={isBusy}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
) : null}
|
||||
{done && (
|
||||
<div className="success-container">
|
||||
<p className="reset-sent-text">A password reset link was sent.</p>
|
||||
</div>
|
||||
)}
|
||||
</> : null}
|
||||
{resetMode === "resetPassword" ? <>
|
||||
<div className="password-reset-header">
|
||||
<h2 className="password-reset-header-text">
|
||||
Enter New Password
|
||||
</h2>
|
||||
</div>
|
||||
{!done && isCodeValid !== null && isCodeValid ? (
|
||||
<form>
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="passwordInput"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={isBusy}
|
||||
></input>
|
||||
<label htmlFor="emailInput" className="form-label">
|
||||
Enter new password
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-floating">
|
||||
<input
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="confirmPasswordInput"
|
||||
placeholder="Confirm Password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
disabled={isBusy}
|
||||
></input>
|
||||
<label htmlFor="confirmPasswordInput" className="form-label">
|
||||
Confirm password
|
||||
</label>
|
||||
</div>
|
||||
<div className="alert-box">
|
||||
{"" !== notice && (
|
||||
<div className="password-reset-alert" role="alert">
|
||||
{notice}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="password-reset-button-wrapper">
|
||||
<Button
|
||||
className="primary-button"
|
||||
onClick={(e) => handleConfirmPasswordReset(e)}
|
||||
labelText="Submit"
|
||||
disabled={isBusy}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
) : null}
|
||||
{!done && isCodeValid !== null && !isCodeValid && (
|
||||
<div className="success-container">
|
||||
<p className="reset-sent-text">Bad or already used password reset code.</p>
|
||||
</div>
|
||||
)}
|
||||
{done && (
|
||||
<div className="success-container">
|
||||
<p className="reset-sent-text">Password changed.</p>
|
||||
</div>
|
||||
)}
|
||||
</> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordResetPage;
|
||||
8
src/Components/Account/SettingsPage.js
Normal file
8
src/Components/Account/SettingsPage.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useState, useContext, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const SettingsPage = () => {
|
||||
return <div>Settings Page</div>;
|
||||
};
|
||||
|
||||
export default SettingsPage;
|
||||
133
src/Components/Case/CaseDetailsPage.js
Normal file
133
src/Components/Case/CaseDetailsPage.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { useEffect, useContext, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { DetailCard } from "../../pageElements/DetailCard.js";
|
||||
import Button from "../../pageElements/Button";
|
||||
import CreateModal from "../Modals/CreateModal.js";
|
||||
import UploadModal from "../Modals/UploadModal.js";
|
||||
import { db } from "../../firebase";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext.js";
|
||||
import LoadingSpinner from "../../pageElements/LoadingSpinner";
|
||||
import { doc, onSnapshot } from "firebase/firestore";
|
||||
|
||||
const CaseDetailsPage = () => {
|
||||
const { caseId } = useParams();
|
||||
const [subCase, setSubCase] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
const [showUploadModal, setShowUploadModal] = useState();
|
||||
const [showCreateModal, setShowCreateModal] = useState();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { appState } = useContext(AppContext);
|
||||
const { group } = appState;
|
||||
const appUserId = group ? group.appUserId : null;
|
||||
const message = "Parsing document. This may take several minutes.";
|
||||
|
||||
const handleNavigate = () => {
|
||||
navigate("/documents");
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
navigate("/cases");
|
||||
};
|
||||
|
||||
function getCase() {
|
||||
const docRef = doc(db, "cases", caseId);
|
||||
const unsub = onSnapshot(docRef, (snapshot) => {
|
||||
if (snapshot.exists() && snapshot.data().ownerId === appUserId) {
|
||||
setSubCase({ ...snapshot.data(), id: snapshot.id });
|
||||
} else {
|
||||
setSubCase(null);
|
||||
}
|
||||
});
|
||||
return unsub;
|
||||
}
|
||||
|
||||
useEffect(getCase, [caseId, appUserId]);
|
||||
|
||||
const handleSuccess = () => {
|
||||
setIsLoading(true);
|
||||
setShowUploadModal(false);
|
||||
setTimeout(handleNavigate, 40000);
|
||||
};
|
||||
|
||||
const ButtonContent = () => {
|
||||
return (
|
||||
<div className="details-button-container">
|
||||
<div className="details-butn-wrapper">
|
||||
<Button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
labelText="Edit"
|
||||
className="primary-button"
|
||||
/>
|
||||
</div>
|
||||
<div className="details-button-box">
|
||||
<Button
|
||||
className="secondary-button back-button"
|
||||
onClick={handleBack}
|
||||
labelText="Back"
|
||||
/>
|
||||
<div className="upload-button-box">
|
||||
<Button
|
||||
className="p-2 mr-2 primary-button"
|
||||
onClick={() => setShowUploadModal(true)}
|
||||
labelText="Upload Document"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const HeadingContent = () => {
|
||||
return (
|
||||
<div className="details-heading-container">
|
||||
<h3 className="details-heading">Case Details:</h3>
|
||||
<div className="details-subhead">
|
||||
<h3 className="details-heading2">
|
||||
{subCase?.caption} v. {subCase?.captionTwo}
|
||||
</h3>
|
||||
<div className="pheno-phun">
|
||||
<h3 className="details-heading3">Index Number:</h3>
|
||||
<h3 className="details-heading4">{subCase?.caseNumber}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const showUp = showUploadModal && subCase !== null ? true : false;
|
||||
const showCreate = showCreateModal && subCase !== null ? true : false;
|
||||
return (
|
||||
<div className="details-container">
|
||||
{isLoading ? <LoadingSpinner message={message} /> : null}
|
||||
{!isLoading ? <HeadingContent /> : null}
|
||||
{!isLoading ? (
|
||||
showUp ? (
|
||||
<UploadModal
|
||||
setShowModal={setShowUploadModal}
|
||||
setIsLoading={setIsLoading}
|
||||
handleSuccess={handleSuccess}
|
||||
caseData={subCase}
|
||||
/>
|
||||
) : null
|
||||
) : null}
|
||||
{!isLoading ? (
|
||||
showCreate ? (
|
||||
<CreateModal setShowModal={setShowCreateModal} caseData={subCase} />
|
||||
) : null
|
||||
) : null}
|
||||
{!isLoading ? (
|
||||
subCase !== null ? (
|
||||
<DetailCard data={subCase} />
|
||||
) : null
|
||||
) : null}
|
||||
{!isLoading ? <ButtonContent /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaseDetailsPage;
|
||||
152
src/Components/Case/CaseListPage.js
Normal file
152
src/Components/Case/CaseListPage.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import React, { useEffect, useContext, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CaseCard } from "../../pageElements/Cards.js";
|
||||
import CreateModal from "../Modals/CreateModal.js";
|
||||
import { db } from "../../firebase";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext";
|
||||
import SelectDropdown from "../../pageElements/SelectDropdown";
|
||||
import { collection, query, where, onSnapshot } from "firebase/firestore";
|
||||
import ListPagination from "../../pageElements/ListPagination.js";
|
||||
import Button from "../../pageElements/Button";
|
||||
|
||||
const CaseListPage = ({ perPage }) => {
|
||||
const navigate = useNavigate();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [allCases, setAllCases] = useState(null);
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [order, setOrder] = useState("caption");
|
||||
const { appState } = useContext(AppContext);
|
||||
const { group } = appState;
|
||||
const appUserId = group ? group.appUserId : null;
|
||||
|
||||
function getCases() {
|
||||
if (!appUserId) {
|
||||
setAllCases([]);
|
||||
return;
|
||||
}
|
||||
const q = query(collection(db, "cases"), where("ownerId", "==", appUserId));
|
||||
const unsub = onSnapshot(q, (snapshot) => {
|
||||
const data = snapshot.docs.map((doc) => doc.data());
|
||||
if (order === "caption") {
|
||||
data.sort((a, b) => {
|
||||
return a.caption.toLowerCase() > b.caption.toLowerCase() ? 1 : -1;
|
||||
});
|
||||
} else if (order === "caption2") {
|
||||
data.sort((a, b) => {
|
||||
return a.caption.toLowerCase() < b.caption.toLowerCase() ? 1 : -1;
|
||||
});
|
||||
} else if (order === "filedDate") {
|
||||
data.sort((a, b) => {
|
||||
return a.filedDate > b.filedDate ? 1 : -1;
|
||||
});
|
||||
} else if (order === "filedDate2") {
|
||||
data.sort((a, b) => {
|
||||
return a.filedDate < b.filedDate ? 1 : -1;
|
||||
});
|
||||
}
|
||||
const pages = Math.ceil(data.length / perPage);
|
||||
if (page > pages) {
|
||||
setPage(pages);
|
||||
}
|
||||
setTotalPages(pages);
|
||||
setAllCases(data);
|
||||
});
|
||||
return unsub;
|
||||
}
|
||||
const createNewButton = () => {
|
||||
return (
|
||||
<div className="create-new-box">
|
||||
<Button
|
||||
className="primary-button"
|
||||
onClick={() => setShowModal(!showModal)}
|
||||
labelText="Create New Case"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const dropDownOptions = [
|
||||
{ label: "Case name a - z", value: "caption" },
|
||||
{ label: "Case name z - a", value: "caption2" },
|
||||
{ label: "Date filed 1 - 10", value: "filedDate" },
|
||||
{ label: "Date filed 10 - 1", value: "filedDate2" },
|
||||
];
|
||||
|
||||
useEffect(getCases, [appUserId, order]);
|
||||
|
||||
async function deleteData() {
|
||||
console.log("delete data");
|
||||
}
|
||||
|
||||
const handleView = (caseId) => {
|
||||
navigate(`/casedetails/${caseId}`);
|
||||
};
|
||||
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="doc-list-header">
|
||||
<h2 className="doc-header-text">Active Cases</h2>
|
||||
<div className="user-name-container" style={{ marginLeft: "4px" }}>
|
||||
{group.firm}
|
||||
</div>
|
||||
</div>
|
||||
{showModal ? <CreateModal setShowModal={setShowModal} /> : <></>}
|
||||
{allCases === null ? <div>Loading...</div> : null}
|
||||
{allCases !== null ? (
|
||||
allCases.length > 0 ? (
|
||||
<>
|
||||
<div className="d-flex justify-content-end mb-3">
|
||||
<SelectDropdown
|
||||
dropDownOptions={dropDownOptions}
|
||||
titleText="Sort Cases"
|
||||
handleSelect={setOrder}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{allCases
|
||||
.slice((page - 1) * perPage, page * perPage)
|
||||
.map((c, i) => (
|
||||
<CaseCard
|
||||
key={`linkcard-${i}`}
|
||||
caption={c.caption}
|
||||
captionTwo={c.captionTwo}
|
||||
jurisdiction={c.jurisdiction}
|
||||
filedDate={c.filedDate}
|
||||
caseNumber={`Index no. ${c.caseNumber}`}
|
||||
caseId={c.caseId}
|
||||
labelText="View"
|
||||
onClick={handleView}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{totalPages > 1 ? (
|
||||
<div className="mb-3">
|
||||
<ListPagination
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
setPage={setPage}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{createNewButton()}
|
||||
</>
|
||||
) : (
|
||||
<div className="nodocs-text-container">
|
||||
<p> You have not created any cases yet.</p>
|
||||
<p>
|
||||
To get started, click "Create New Case" and enter case
|
||||
information.
|
||||
</p>
|
||||
{createNewButton()}
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaseListPage;
|
||||
248
src/Components/Dashboard/Dashboard.js
Normal file
248
src/Components/Dashboard/Dashboard.js
Normal file
@@ -0,0 +1,248 @@
|
||||
import React, { useState, useContext, useEffect } from "react";
|
||||
import { CaseCard } from "../../pageElements/Cards.js";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
ArrowRight,
|
||||
Activity,
|
||||
FileEarmarkText,
|
||||
Pencil,
|
||||
Clock,
|
||||
} from "react-bootstrap-icons";
|
||||
import { db } from "../../firebase.js";
|
||||
import { collection, onSnapshot, query, where } from "firebase/firestore";
|
||||
import { Typeahead } from "react-bootstrap-typeahead";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext.js";
|
||||
import UploadModal from "../Modals/UploadModal.js";
|
||||
import Button from "react-bootstrap/Button";
|
||||
|
||||
const Dashboard = () => {
|
||||
const navigate = useNavigate();
|
||||
const [allCases, setAllCases] = useState(null);
|
||||
const [selectedCase, setSelectedCase] = useState(null);
|
||||
const [showModal, setShowModal] = useState();
|
||||
const [docCount, setDocCount] = useState();
|
||||
const [responseCount, setResponseCount] = useState();
|
||||
const { appState } = useContext(AppContext);
|
||||
const { group } = appState;
|
||||
const appUserId = group ? group.appUserId : null;
|
||||
|
||||
function getCases() {
|
||||
if (!appUserId) {
|
||||
setAllCases([]);
|
||||
return;
|
||||
}
|
||||
const q = query(collection(db, "cases"), where("ownerId", "==", appUserId));
|
||||
const unsub = onSnapshot(q, (snapshot) => {
|
||||
const data = snapshot.docs.map((doc) => doc.data());
|
||||
data.sort((a, b) => {
|
||||
return a.filedDate < b.filedDate ? 1 : -1;
|
||||
});
|
||||
setAllCases(data);
|
||||
});
|
||||
return unsub;
|
||||
}
|
||||
|
||||
function getStats() {
|
||||
if (!appUserId) {
|
||||
return;
|
||||
}
|
||||
const q = query(
|
||||
collection(db, "documents"),
|
||||
where("ownerId", "==", appUserId)
|
||||
);
|
||||
const unsub = onSnapshot(q, (snapshot) => {
|
||||
const data = snapshot.docs.map((doc) => doc.data());
|
||||
if (data) {
|
||||
setDocCount(data?.length);
|
||||
const respCount = data?.reduce(function (acc, obj) {
|
||||
return acc + obj.responseGenerations;
|
||||
}, 0);
|
||||
setResponseCount(respCount);
|
||||
}
|
||||
});
|
||||
return unsub;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!appUserId) {
|
||||
return;
|
||||
}
|
||||
const unsubCases = getCases();
|
||||
const unsubStats = getStats();
|
||||
return () => {
|
||||
typeof unsubCases !== "undefined" && unsubCases();
|
||||
typeof unsubStats !== "undefined" && unsubStats();
|
||||
};
|
||||
}, [appUserId]);
|
||||
|
||||
const handleViewClick = (caseId) => {
|
||||
navigate(`/casedetails/${caseId}`);
|
||||
};
|
||||
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dashboard-header">
|
||||
<h2 className="doc-header-text">
|
||||
{!!(group.firstName && group.lastName) ? (
|
||||
<span>
|
||||
{group.firstName} {group.lastName}
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</h2>
|
||||
<div className="user-name-container2">{group.firm}</div>
|
||||
</div>
|
||||
<div className="app-divider dash-divider" />
|
||||
<div className="stats-container">
|
||||
<div className="stats-row-box">
|
||||
<div className="stats-unit-wrapper">
|
||||
<div className="stats-icon-wrapper">
|
||||
<Activity style={{ color: "#f27300" }} />
|
||||
</div>
|
||||
{allCases?.length >= 1 ? (
|
||||
<div>
|
||||
<p className="stats-text">
|
||||
{allCases?.length === 1
|
||||
? `${allCases?.length} active case`
|
||||
: `${allCases?.length} active cases`}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<p className="stats-text">
|
||||
{allCases?.length} active cases - Click on the "Cases" tab,
|
||||
above, and create a case to get started.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="stats-unit-wrapper">
|
||||
{allCases?.length >= 1 ? (
|
||||
<>
|
||||
<div className="stats-icon-wrapper">
|
||||
<FileEarmarkText style={{ color: "#6da8dc" }} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="stats-text">
|
||||
{docCount === 1
|
||||
? `${docCount} document uploaded`
|
||||
: `${docCount} documents uploaded`}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="stats-unit-wrapper">
|
||||
{responseCount > 0 ? (
|
||||
<>
|
||||
<div className="stats-icon-wrapper">
|
||||
<Pencil style={{ color: "#767676" }} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="stats-text">
|
||||
{responseCount}
|
||||
{responseCount < 2
|
||||
? " response Novodrafted"
|
||||
: " responses Novodrafted"}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<div className="stats-unit-wrapper">
|
||||
{responseCount > 0 ? (
|
||||
<>
|
||||
<div className="stats-icon-wrapper">
|
||||
<Clock style={{ color: "#f27300" }} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="stats-text">
|
||||
{Math.round(responseCount * 3.2989 * 100) / 100} hours saved
|
||||
using Novodraft
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{allCases === null ? <div>Loading...</div> : null}
|
||||
{allCases !== null ? (
|
||||
<div>
|
||||
<div className="dashboard-filter mb-3">
|
||||
<Typeahead
|
||||
className="flex-grow-1"
|
||||
id="case-search"
|
||||
labelKey="caseNumber"
|
||||
options={allCases}
|
||||
minLength={1}
|
||||
placeholder="Search by Case Number"
|
||||
onChange={(selected) => {
|
||||
let item = null;
|
||||
for (const currentItem of selected) {
|
||||
item = { ...currentItem };
|
||||
}
|
||||
setSelectedCase(item);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={selectedCase === null}
|
||||
onClick={() => handleViewClick(selectedCase?.caseId)}
|
||||
>
|
||||
View Case
|
||||
</Button>
|
||||
<Button
|
||||
disabled={selectedCase === null}
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
Express Document Upload
|
||||
</Button>
|
||||
</div>
|
||||
{allCases.slice(0, 3).map((c, i) => (
|
||||
<CaseCard
|
||||
key={`linkcard-${i}`}
|
||||
caption={c.caption}
|
||||
captionTwo={c.captionTwo}
|
||||
jurisdiction={c.jurisdiction}
|
||||
caseNumber={`Index no. ${c.caseNumber}`}
|
||||
filedDate={c.filedDate}
|
||||
caseId={c.caseId}
|
||||
labelText="View"
|
||||
onClick={handleViewClick}
|
||||
/>
|
||||
))}
|
||||
{allCases?.length > 3 ? (
|
||||
<div className="view-all-link-box mb-3">
|
||||
<div className="view-all-wrapper">
|
||||
<Link className="view-all-link" to="/cases">
|
||||
View All{" "}
|
||||
<span className="arrow-wrapper">
|
||||
<ArrowRight />
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{showModal && selectedCase !== null ? (
|
||||
<UploadModal setShowModal={setShowModal} caseData={selectedCase} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
606
src/Components/Document/DocEditPage.js
Normal file
606
src/Components/Document/DocEditPage.js
Normal file
@@ -0,0 +1,606 @@
|
||||
import { useState, useContext, useEffect } from "react";
|
||||
import NewYorkCaption from "./captionHeaders/newYorkCaption.js";
|
||||
import NewJerseyCaption from "./captionHeaders/newJerseyCaption.js";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import EditElement from "../../pageElements/EditElement";
|
||||
import { db } from "../../firebase";
|
||||
import {
|
||||
updateDoc,
|
||||
onSnapshot,
|
||||
doc,
|
||||
getDoc,
|
||||
increment,
|
||||
} from "firebase/firestore";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext";
|
||||
import Button from "../../pageElements/Button";
|
||||
import ConfirmModal from "../Modals/ConfirmModal";
|
||||
import LoadingSpinner from "../../pageElements/LoadingSpinner";
|
||||
import { scrubAll } from "../../Utils/String";
|
||||
import { docEditCopy } from "../../Constants/Copy/docEditCopy.js";
|
||||
import "../../styles/docedit-page.scss";
|
||||
|
||||
const DocEditPage = () => {
|
||||
const { documentId, responseGenerations, caseId, documentType } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [parsedRogs, setParsedRogs] = useState();
|
||||
const [responses, setResponses] = useState([1]);
|
||||
const [fetchedCase, setFetchedCase] = useState();
|
||||
const [showInputEle, setShowInputEle] = useState(true);
|
||||
const [document, setDocument] = useState(null);
|
||||
const { appState } = useContext(AppContext);
|
||||
const { group } = appState;
|
||||
const appUserId = group ? group.appUserId : null;
|
||||
const [docId, setDocId] = useState(documentId);
|
||||
const [saveDocumentId, setSaveDocumentId] = useState("");
|
||||
const [showSaveModal, setShowSaveModal] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [responsesCreated, setResponsesCreated] = useState(
|
||||
parseInt(responseGenerations)
|
||||
);
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
// For document generation:
|
||||
const state = group ? group?.state : null;
|
||||
const lawFirm = group ? group?.firm : null;
|
||||
const streetAdd = group ? group?.streetAddress : null;
|
||||
const city = group ? group.city : null;
|
||||
const usState = group ? group.state : null;
|
||||
const zipCode = group ? group.zipCode : null;
|
||||
const telephone = group ? group.telephone : null;
|
||||
|
||||
const displayCopy =
|
||||
state === "New York"
|
||||
? docEditCopy.NewYork
|
||||
: state === "New Jersey"
|
||||
? docEditCopy.NewJersey
|
||||
: docEditCopy.NewYork;
|
||||
|
||||
const headerPicker = () => {
|
||||
console.log("state", state);
|
||||
switch (state) {
|
||||
case "New York":
|
||||
return (
|
||||
<NewYorkCaption
|
||||
displayCopy={displayCopy}
|
||||
fetchedCase={fetchedCase}
|
||||
onScrollClick={onScrollClick}
|
||||
/>
|
||||
);
|
||||
case "New Jersey":
|
||||
return (
|
||||
<NewJerseyCaption
|
||||
displayCopy={displayCopy}
|
||||
fetchedCase={fetchedCase}
|
||||
onScrollClick={onScrollClick}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<NewYorkCaption
|
||||
displayCopy={displayCopy}
|
||||
fetchedCase={fetchedCase}
|
||||
onScrollClick={onScrollClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const baseUrl = "http://localhost:4000";
|
||||
|
||||
const headerString =
|
||||
documentType === "interrogatories" ? (
|
||||
"Response to Interrogatories"
|
||||
) : documentType === "admissions" ? (
|
||||
"Response to Request for Admissions"
|
||||
) : documentType === "production" ? (
|
||||
"Response to Request for Production"
|
||||
) : documentType === "combined-numbered" ? (
|
||||
"Response to Interrogatories and Request for Production"
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
const servingParty =
|
||||
fetchedCase?.clientPosition === "Defendant"
|
||||
? fetchedCase?.plaintiffParties
|
||||
: fetchedCase?.defendantParties;
|
||||
|
||||
const messages = [
|
||||
"Generating responses. Please be patient, this may take several minutes.",
|
||||
'Do not click "back" on your browser.',
|
||||
];
|
||||
|
||||
/*
|
||||
* GET docuement (info) from Firebase
|
||||
* */
|
||||
|
||||
useEffect(() => {
|
||||
if (!appUserId || !docId) {
|
||||
setDocument(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
getCase(caseId);
|
||||
getDocument();
|
||||
} catch (err) {
|
||||
console.log("Error:", err);
|
||||
}
|
||||
}, [appUserId, docId, caseId]);
|
||||
|
||||
/*
|
||||
* GET parsed requests.
|
||||
* GET completions if already generated
|
||||
* */
|
||||
useEffect(() => {
|
||||
if (!documentId || !documentType) {
|
||||
return;
|
||||
}
|
||||
// TODO: *BUG* still sometimes gens repsonses after first one, maybe state is not updating
|
||||
getParsedRequests(documentId, documentType);
|
||||
if (responsesCreated > 0) {
|
||||
getCompletions(documentId)
|
||||
.then((data) => {
|
||||
const resp = data[0].responses.map((item, index) => {
|
||||
// NEVER CHANGE THIS:
|
||||
return { showInputEle: false, resp: item.text, index: index };
|
||||
});
|
||||
setResponses(resp);
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
}
|
||||
}, [documentId]);
|
||||
|
||||
/*
|
||||
* If response completions = 0, generate responses
|
||||
*/
|
||||
|
||||
useEffect(() => {
|
||||
if (responsesCreated > 0) {
|
||||
return;
|
||||
}
|
||||
if (responsesCreated < 1) {
|
||||
try {
|
||||
const res = generateResponses(documentId);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}, [responsesCreated, documentId]);
|
||||
|
||||
async function getCase(parentCaseId) {
|
||||
try {
|
||||
const docRef = doc(db, "cases", `${parentCaseId}`);
|
||||
const docSnap = await getDoc(docRef);
|
||||
if (docSnap.exists()) {
|
||||
const _case = docSnap.data();
|
||||
_case.id = docSnap._key.path.segments[1];
|
||||
setFetchedCase(_case);
|
||||
} else {
|
||||
console.log("DB item does not exist");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`A system error has occurred: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getDocument() {
|
||||
const docRef = doc(db, "documents", `${docId}`);
|
||||
const unsub = onSnapshot(docRef, (snapshot) => {
|
||||
if (snapshot.exists() && snapshot.data().ownerId === appUserId) {
|
||||
setDocument({ ...snapshot.data(), documentId: snapshot.id });
|
||||
} else {
|
||||
setDocument(null);
|
||||
}
|
||||
});
|
||||
return unsub;
|
||||
}
|
||||
|
||||
async function getParsedRequests(documentId) {
|
||||
if (documentId) {
|
||||
const docType = String(documentType);
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${baseUrl}/getParsedRequests/${documentId}/${docType}`,
|
||||
{
|
||||
method: "GET",
|
||||
}
|
||||
);
|
||||
const res = await response.json();
|
||||
|
||||
const req = res[0].requests;
|
||||
if (req.length > 1) {
|
||||
setParsedRogs(req);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Error occured:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getCompletions(docId) {
|
||||
const docType = String(documentType);
|
||||
const response = await fetch(`${baseUrl}/completions/${docId}/${docType}`, {
|
||||
method: "GET",
|
||||
});
|
||||
const req = await response.json();
|
||||
return req;
|
||||
}
|
||||
|
||||
async function generateResponses(documentId) {
|
||||
if (responsesCreated > 0) {
|
||||
return;
|
||||
}
|
||||
const docType = String(documentType);
|
||||
let mode;
|
||||
//TODO: remove isRequests var
|
||||
const isRequests = false;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
fetch(
|
||||
`${baseUrl}/genResponseFromArray/${documentId}/${docType}/${isRequests}`,
|
||||
{
|
||||
method: "GET",
|
||||
}
|
||||
)
|
||||
.then(function (response) {
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
const respArray = data[0].responses;
|
||||
const result = respArray?.map((item, index) => {
|
||||
let obj = {};
|
||||
obj["showInputEle"] = false;
|
||||
obj["resp"] = item.text;
|
||||
obj["index"] = index;
|
||||
return obj;
|
||||
});
|
||||
setResponses(result);
|
||||
updateRespGenerationCount(documentId);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function postEditedResponses() {
|
||||
let obj = {};
|
||||
obj["type"] = documentType;
|
||||
obj["id"] = docId;
|
||||
obj["requests"] = responses;
|
||||
const data = JSON.stringify(obj);
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/storecompletions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
json: true,
|
||||
body: data,
|
||||
});
|
||||
const res = response;
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error("Error sending responses to the server:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function createAndReturnDocx() {
|
||||
const response = await handleCreateDocx();
|
||||
if (response.status === 200) {
|
||||
setTimeout(getDocx, 5000);
|
||||
setTimeout(cleanUpDocx, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanUpDocx() {
|
||||
const docId = documentId;
|
||||
const reqType = documentType;
|
||||
fetch(`${baseUrl}/cleanUpDocx/${docId}/${reqType}`, {
|
||||
method: "GET",
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
async function getDocx() {
|
||||
const docId = documentId;
|
||||
const reqType = documentType;
|
||||
fetch(`${baseUrl}/getDocx/${docId}/${reqType}`, {
|
||||
method: "GET",
|
||||
}).then((response) => {
|
||||
response.blob().then((blob) => {
|
||||
let url = window.URL.createObjectURL(blob);
|
||||
let a = window.document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${reqType}-repsonse.docx`;
|
||||
a.click();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function handleCreateDocx() {
|
||||
// TODO: compare to see if any changes made before gen, save those
|
||||
const docId = documentId;
|
||||
const leadAttorneys = fetchedCase?.leadAttorneys
|
||||
? fetchedCase?.leadAttorneys
|
||||
: null;
|
||||
const reqType = documentType;
|
||||
const captionOne = fetchedCase.caption;
|
||||
const captionTwo = fetchedCase.captionTwo;
|
||||
const caseNum = fetchedCase.caseNumber;
|
||||
const plaintiffs = fetchedCase.plaintiffParties;
|
||||
const defendants = fetchedCase.defendantParties;
|
||||
const venued = fetchedCase.venue;
|
||||
const juris = fetchedCase.jurisdiction;
|
||||
const judge = fetchedCase.judge;
|
||||
const clientPosition = fetchedCase?.clientPosition
|
||||
? fetchedCase?.clientPosition
|
||||
: null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/genDocx/${docId}/${reqType}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
caseCaption1: captionOne,
|
||||
caseCaption2: captionTwo,
|
||||
caseNumber: caseNum,
|
||||
clientPosition: clientPosition,
|
||||
defendant: defendants,
|
||||
firm: lawFirm,
|
||||
firmCity: city,
|
||||
firmState: usState,
|
||||
firmStreetAddress: streetAdd,
|
||||
firmZip: zipCode,
|
||||
judge: judge,
|
||||
jurisdiction: juris,
|
||||
leadAttorneys: leadAttorneys,
|
||||
plaintiff: plaintiffs,
|
||||
state: usState,
|
||||
tel: telephone,
|
||||
venue: venued,
|
||||
}),
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending responses to the server:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRespGenerationCount(docId) {
|
||||
await updateDoc(doc(db, "documents", docId), {
|
||||
responseGenerations: increment(1),
|
||||
});
|
||||
setResponsesCreated((responsesCreated) => responsesCreated + 1);
|
||||
}
|
||||
|
||||
function saveDocument(documentId) {
|
||||
setSaveDocumentId(documentId);
|
||||
setShowSaveModal(!showSaveModal);
|
||||
}
|
||||
|
||||
async function postEditedResponses() {
|
||||
let obj = {};
|
||||
|
||||
obj["type"] = "INTERROGATORY";
|
||||
obj["id"] = docId;
|
||||
obj["requests"] = responses;
|
||||
const data = JSON.stringify(obj);
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/storeeditedcompletions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
json: true,
|
||||
body: data,
|
||||
});
|
||||
const res = response;
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error("Error sending responses to the server:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleBlur = (e, i) => {
|
||||
const newSelectedResponses = responses?.map((r) => {
|
||||
if (r.index === i) {
|
||||
return {
|
||||
...r,
|
||||
showInputEle: !showInputEle,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
});
|
||||
setResponses(newSelectedResponses);
|
||||
};
|
||||
|
||||
const handleFocus = (arg, i) => {
|
||||
const newSelectedResponses = responses?.map((r) => {
|
||||
if (r.index === i) {
|
||||
return {
|
||||
...r,
|
||||
showInputEle: !showInputEle,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
});
|
||||
setResponses(newSelectedResponses);
|
||||
};
|
||||
|
||||
const handleEditValue = (e, i) => {
|
||||
const editingRes = responses?.map((r) => {
|
||||
if (r.index === i) {
|
||||
return {
|
||||
...r,
|
||||
resp: e.target.value,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
});
|
||||
setResponses(editingRes);
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
navigate(`/documents`);
|
||||
};
|
||||
|
||||
const handleCancelSave = () => {
|
||||
setShowSaveModal(false);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
postEditedResponses();
|
||||
setShowSaveModal(false);
|
||||
};
|
||||
|
||||
const handleConfirmSave = () => {
|
||||
setShowSaveModal(true);
|
||||
};
|
||||
|
||||
const onScrollClick = () => {
|
||||
const bottom = window.document.scrollingElement.scrollHeight;
|
||||
const place = window.scrollY;
|
||||
if (place > bottom / 2) {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
} else if (place < bottom / 2) {
|
||||
window.scrollTo({ top: bottom, behavior: "smooth" });
|
||||
}
|
||||
};
|
||||
|
||||
const numberingContent = (i) => {
|
||||
let numText;
|
||||
if (documentType === "interrogatories") {
|
||||
numText = "INTERROGATORY NO..";
|
||||
} else if (documentType === "admissions") {
|
||||
numText = "REQUEST FOR ADMISSION NO.";
|
||||
} else if (documentType === "production") {
|
||||
numText = "REQUEST FOR PRODUCTION NO.";
|
||||
} else {
|
||||
numText = "REQUEST NO.";
|
||||
}
|
||||
return <div className="numbering-container">{`${numText}${i + 1} `}</div>;
|
||||
};
|
||||
|
||||
const respondent =
|
||||
fetchedCase?.clientPosition == "Defendant"
|
||||
? fetchedCase?.defendantParties
|
||||
: fetchedCase?.plaintiffParties;
|
||||
|
||||
const fooBar =
|
||||
fetchedCase?.clientPosition == "Defendant"
|
||||
? fetchedCase?.plaintiffParties
|
||||
: fetchedCase?.defendantParties;
|
||||
|
||||
const editingContent = () => {
|
||||
return (
|
||||
<>
|
||||
{headerPicker()}
|
||||
<div className="doc-editing-wrapper">
|
||||
<div className="pleading-header">{headerString}</div>
|
||||
<div>
|
||||
{displayCopy.prelimaryStatement(
|
||||
documentType,
|
||||
respondent,
|
||||
servingParty
|
||||
)}
|
||||
</div>
|
||||
<div className="doc-editing-sub-wrapper">
|
||||
{parsedRogs?.map((rog, i) => (
|
||||
<div className="req-item-outer-div">
|
||||
<div className="request-text-up">
|
||||
{numberingContent(i)}
|
||||
{scrubAll(rog?.text)}
|
||||
</div>
|
||||
<div className="req-item-inner-div">
|
||||
<EditElement
|
||||
value={
|
||||
responses && responses.length > 2
|
||||
? // NEVER CHANGE THIS
|
||||
responses[i].resp
|
||||
: null
|
||||
}
|
||||
setShowInputEle={setShowInputEle}
|
||||
showInputEle={
|
||||
responses && responses.length > 2
|
||||
? responses[i].showInputEle
|
||||
: null
|
||||
}
|
||||
handleFocus={handleFocus}
|
||||
handleEditValue={handleEditValue}
|
||||
handleBlur={handleBlur}
|
||||
i={i}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="doc-editing-button-container">
|
||||
<div className="generate-button-box">
|
||||
{/* TODO: add tooltip: "This will save the document and create a .docx file that will (be downloaded? saved somewhere?" */}
|
||||
<Button
|
||||
className="primary-button create-gen-button"
|
||||
onClick={createAndReturnDocx}
|
||||
labelText={"Create .docx File"}
|
||||
/>
|
||||
</div>
|
||||
<div className="details-button-box">
|
||||
<div>
|
||||
<Button
|
||||
className="pt-2 pb-2 mr-4 secondary-button edit-back-button"
|
||||
onClick={handleBack}
|
||||
labelText="Back To Documents Page"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className="pt-2 pb-2 mr-2 primary-button"
|
||||
onClick={handleConfirmSave}
|
||||
labelText="Save Changes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showSaveModal && documentId !== null ? (
|
||||
<ConfirmModal
|
||||
onCancel={handleCancelSave}
|
||||
onConfirm={handleSave}
|
||||
buttonLabelText="Confirm Save"
|
||||
modalText={
|
||||
"Are you sure? This will overrwrite any previously-saved edits."
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const readyToEdit = fetchedCase && responses.length > 1;
|
||||
|
||||
return isLoading ? (
|
||||
<LoadingSpinner message={messages[0]} />
|
||||
) : readyToEdit ? (
|
||||
editingContent()
|
||||
) : (
|
||||
<div className="docedit-errstate-container">
|
||||
<div className="nodocs-text-container">
|
||||
<p>
|
||||
{" "}
|
||||
Response content for this document cannot be displayed at this time.
|
||||
</p>
|
||||
<p>Support has been notified and will respond within 24 hours.</p>
|
||||
<Link to="/dashboard">Return to Dashboard</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocEditPage;
|
||||
266
src/Components/Document/DocumentListPage.js
Normal file
266
src/Components/Document/DocumentListPage.js
Normal file
@@ -0,0 +1,266 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext";
|
||||
import SelectDropdown from "../../pageElements/SelectDropdown";
|
||||
import { DocCard } from "../../pageElements/Cards";
|
||||
import { db } from "../../firebase";
|
||||
import {
|
||||
collection,
|
||||
deleteDoc,
|
||||
doc,
|
||||
documentId,
|
||||
onSnapshot,
|
||||
query,
|
||||
where,
|
||||
} from "firebase/firestore";
|
||||
import ListPagination from "../../pageElements/ListPagination";
|
||||
import ConfirmModal from "../Modals/ConfirmModal";
|
||||
import "../../styles/doclist-page.scss";
|
||||
|
||||
const DocumentListPage = ({ perPage }) => {
|
||||
const navigate = useNavigate();
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [selectedDocumentId, setSelectedDocumentId] = useState(null);
|
||||
const [allDocs, setAllDocs] = useState(null);
|
||||
const { appState } = useContext(AppContext);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const { group } = appState;
|
||||
const [page, setPage] = useState(1);
|
||||
const [order, setOrder] = useState("parentCaseName");
|
||||
const appUserId = group ? group.appUserId : null;
|
||||
const baseUrl = "http://localhost:4000";
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [selectedDocumentType, setSelectedDocumentType] = useState();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
function getDocuments() {
|
||||
if (!appUserId) {
|
||||
setAllDocs([]);
|
||||
return;
|
||||
}
|
||||
const q = query(
|
||||
collection(db, "documents"),
|
||||
where("ownerId", "==", appUserId)
|
||||
);
|
||||
const unsub = onSnapshot(q, (snapshot) => {
|
||||
const data = snapshot.docs.map((doc) => ({
|
||||
...doc.data(),
|
||||
documentId: doc.id,
|
||||
}));
|
||||
if (order === "parentCaseName") {
|
||||
data.sort((a, b) => {
|
||||
return a.parentCaseName.toLowerCase() > b.parentCaseName.toLowerCase()
|
||||
? 1
|
||||
: -1;
|
||||
});
|
||||
} else if (order === "parentCaseName2") {
|
||||
data.sort((a, b) => {
|
||||
return a.parentCaseName.toLowerCase() < b.parentCaseName.toLowerCase()
|
||||
? 1
|
||||
: -1;
|
||||
});
|
||||
}
|
||||
const pages = Math.ceil(data.length / perPage);
|
||||
if (page > pages) {
|
||||
setPage(pages);
|
||||
}
|
||||
setTotalPages(pages);
|
||||
setAllDocs(data);
|
||||
});
|
||||
return unsub;
|
||||
}
|
||||
|
||||
useEffect(getDocuments, [appUserId, order]);
|
||||
|
||||
async function deleteFromDb(documentId) {
|
||||
if (isBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsBusy(true);
|
||||
try {
|
||||
const docRef = doc(db, "documents", `${documentId}`);
|
||||
await deleteDoc(docRef);
|
||||
setShowDeleteModal(false);
|
||||
} catch (err) {
|
||||
console.log("Error deleting document from db:", err);
|
||||
}
|
||||
setIsBusy(false);
|
||||
}
|
||||
|
||||
async function deleteFromStorage() {
|
||||
const selectedDoc = allDocs.filter((doc) => {
|
||||
console.log("doc", doc);
|
||||
return doc.documentId == selectedDocumentId;
|
||||
});
|
||||
console.log(
|
||||
"selectedDoc[0].responseGenerations",
|
||||
selectedDoc[0].responseGenerations
|
||||
);
|
||||
const respGens = selectedDoc[0].responseGenerations;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${baseUrl}/deleteDoc/${selectedDocumentId}/${selectedDocumentType}/${respGens}`,
|
||||
{
|
||||
method: "POST",
|
||||
}
|
||||
);
|
||||
const res = response;
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error("Error deleting document from storage:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const dropDownOptions = [
|
||||
{ label: "Associated case name a - z", value: "parentCaseName" },
|
||||
{ label: "Associated case name z - a", value: "parentCaseName2" },
|
||||
];
|
||||
|
||||
async function openDoc(docId) {
|
||||
/*
|
||||
may deprecate this functionality
|
||||
try {
|
||||
window.open(`${baseUrl}/Backend/Documents/Uploads/${docId}`, "_blank");
|
||||
} catch (err) {
|
||||
console.log("Error occurred fetching document:", err);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
function handleNavigate(
|
||||
documentId,
|
||||
responseGenerations,
|
||||
caseId,
|
||||
documentType
|
||||
) {
|
||||
const _id = String(documentId);
|
||||
const _resGen = String(responseGenerations);
|
||||
const parentCaseId = String(caseId);
|
||||
const _docType = String(documentType);
|
||||
navigate(
|
||||
`/docedit/${_id}/${responseGenerations}/${parentCaseId}/${documentType}`
|
||||
);
|
||||
}
|
||||
|
||||
function handleCancelDelete() {
|
||||
if (!isBusy) {
|
||||
setShowDeleteModal(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeleteDocument() {
|
||||
deleteFromDb(selectedDocumentId);
|
||||
deleteFromStorage(selectedDocumentId, selectedDocumentType);
|
||||
}
|
||||
|
||||
function confirmDelete(documentId, docType) {
|
||||
setSelectedDocumentId(documentId);
|
||||
setSelectedDocumentType(docType);
|
||||
setShowDeleteModal(true);
|
||||
}
|
||||
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
console.log("allDocs", allDocs);
|
||||
return (
|
||||
<>
|
||||
<div className="doc-list-header">
|
||||
<h2 className="doc-header-text">Documents</h2>
|
||||
<div className="user-name-container">{group.firm}</div>
|
||||
</div>
|
||||
{allDocs === null ? <div>Loading...</div> : null}
|
||||
{allDocs !== null ? (
|
||||
<div className="dropdown-container">
|
||||
{allDocs.length > 0 ? (
|
||||
<div className="d-flex justify-content-end mb-3">
|
||||
<SelectDropdown
|
||||
dropDownOptions={dropDownOptions}
|
||||
titleText="Sort documents"
|
||||
handleSelect={setOrder}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div className="document-list-lower-wrapper">
|
||||
{allDocs.length > 0 ? (
|
||||
allDocs
|
||||
.slice((page - 1) * perPage, page * perPage)
|
||||
.map((doc, i) => (
|
||||
<DocCard
|
||||
key={`doccard-${i}`}
|
||||
title={doc.docTitle}
|
||||
parentCaseNumber={doc.parentCaseNumber}
|
||||
parentCaseName={doc.parentCaseName}
|
||||
parentCaseId={doc.parentCaseId}
|
||||
docType={doc.docType}
|
||||
documentId={doc.documentId}
|
||||
dateServed={doc.dateServed}
|
||||
openDocument={openDoc}
|
||||
confirmDelete={confirmDelete}
|
||||
handleNavigate={handleNavigate}
|
||||
displayDeleteButton={true}
|
||||
responseGenerations={doc.responseGenerations}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="nodocs-text-container">
|
||||
<p className="nodocs-text">
|
||||
{" "}
|
||||
You have not uploaded any documents yet.
|
||||
</p>
|
||||
<p className="nodocs-light-text">To upload a document:</p>
|
||||
<ol>
|
||||
<li className="nodocs-list-item">
|
||||
Navigate to the cases view (above link in navigation bar).{" "}
|
||||
</li>
|
||||
<li>
|
||||
{" "}
|
||||
Click "View Case" to select the case with which your
|
||||
document will be associated. This will open the Case Detail
|
||||
view.{" "}
|
||||
</li>
|
||||
<li className="nodocs-list-item">
|
||||
Note: If you have not yet created any cases, you will need
|
||||
to create one first - click "Create Case" and follow the
|
||||
prompts.
|
||||
</li>
|
||||
<li className="nodocs-list-item">
|
||||
In the case detail view, click "Upload Case Document" and
|
||||
select a document from your computer for upload.
|
||||
</li>
|
||||
<li className="nodocs-list-item">
|
||||
Your document will be parsed into a processable form. You
|
||||
will then be able to generate a response to the document.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{totalPages > 1 ? (
|
||||
<div className="mb-3">
|
||||
<ListPagination
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
setPage={setPage}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{showDeleteModal && selectedDocumentId !== null ? (
|
||||
<ConfirmModal
|
||||
onCancel={handleCancelDelete}
|
||||
onConfirm={handleDeleteDocument}
|
||||
buttonLabelText="Delete"
|
||||
modalText="Confirm that you want to permanently delete this document."
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentListPage;
|
||||
69
src/Components/Document/captionHeaders/newJerseyCaption.js
Normal file
69
src/Components/Document/captionHeaders/newJerseyCaption.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import Scrollbutton from "react-bootstrap/Button";
|
||||
import { ArrowUp, ArrowDown } from "react-bootstrap-icons";
|
||||
import "../../../styles/njcaption.scss";
|
||||
|
||||
const NewJerseyCaption = (props) => {
|
||||
const { fetchedCase, displayCopy, onScrollClick } = props;
|
||||
return (
|
||||
<div className="njcap-thirds">
|
||||
<div className="njcap-left">
|
||||
<div className="docedit-header-text">Edit Document</div>
|
||||
</div>
|
||||
<div className="njcap-middle">
|
||||
<div className="njcap-middle-left">
|
||||
<div className="njcap-divider"></div>
|
||||
<div className="njcap-parties">{fetchedCase.plaintiffParties}</div>
|
||||
<div className="njcap-plaintiff">
|
||||
<div className="njcap-plaintiff-left"></div>
|
||||
<div className="njcap-plaintiff-right">Plaintiff(s)</div>
|
||||
</div>
|
||||
<div className="njcap-plaintiff">
|
||||
<div className="njcap-plaintiff-left"></div>
|
||||
<div className="njcap-plaintiff-right"></div>
|
||||
<div className="njcap-plaintiff-right">
|
||||
<div className="njcap-vee">v.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="njcap-parties">{fetchedCase.defendantParties}</div>
|
||||
<div className="njcap-plaintiff">
|
||||
<div className="njcap-plaintiff-left"></div>
|
||||
<div className="njcap-plaintiff-right">Defendant(s)</div>
|
||||
</div>
|
||||
<div className="njcap-divider2"></div>
|
||||
</div>
|
||||
<div className="njcap-middle-right">
|
||||
<div className="njcap-middle-right-left">
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
<span>)</span>
|
||||
</div>
|
||||
<div className="njcap-middle-right-right">
|
||||
<div className="njcap-case-info-top">
|
||||
{fetchedCase.jurisdiction}
|
||||
</div>
|
||||
<div className="njcap-case-info">{fetchedCase.venue}</div>
|
||||
<div className="njcap-case-info"></div>
|
||||
<div className="njcap-case-info">
|
||||
Docket No. {fetchedCase.caseNumber}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="njcap-right">
|
||||
<Scrollbutton onClick={onScrollClick} className="scroll-button">
|
||||
Scroll <ArrowUp /> <ArrowDown />
|
||||
</Scrollbutton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewJerseyCaption;
|
||||
61
src/Components/Document/captionHeaders/newYorkCaption.js
Normal file
61
src/Components/Document/captionHeaders/newYorkCaption.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useState } from "react";
|
||||
import Scrollbutton from "react-bootstrap/Button";
|
||||
import { ArrowUp, ArrowDown } from "react-bootstrap-icons";
|
||||
|
||||
const NewYorkCaption = (props) => {
|
||||
const { fetchedCase, displayCopy, onScrollClick } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="docedit-super-row">
|
||||
<div className="docedit-uppercol1"></div>
|
||||
<div className="docedit-uppercol2">
|
||||
<span>{fetchedCase?.jurisdiction}</span>
|
||||
<span>{fetchedCase?.venue}</span>
|
||||
<div className="docedit-uppercol-divider"></div>
|
||||
</div>
|
||||
<div className="docedit-uppercol3"></div>
|
||||
</div>
|
||||
<div className="docedit-header-row">
|
||||
<div className="docedit-header-col1">
|
||||
<div className="docedit-header-text">Edit Document</div>
|
||||
</div>
|
||||
<div className="docedit-header-col2">
|
||||
<div className="header-middle-row1">
|
||||
{fetchedCase?.caption ? fetchedCase?.caption : <></>},
|
||||
</div>
|
||||
<div className="header-middle-row2">
|
||||
<div className="content-spacer"></div>
|
||||
<div className="middle-row-inner">
|
||||
<div>Plaintiff(s),</div>
|
||||
<div className="casenum-box">
|
||||
Index no. {fetchedCase?.caseNumber}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="header-middle-row3">
|
||||
<div>{displayCopy.versusText}</div>
|
||||
<div>Judge: {fetchedCase?.judge}</div>
|
||||
</div>
|
||||
<div className="header-middle-row4">
|
||||
{fetchedCase?.captionTwo ? fetchedCase?.captionTwo : <></>},
|
||||
</div>
|
||||
<div className="header-middle-row5">
|
||||
<div className="content-spacer2"></div>
|
||||
<div>Defendant(s)</div>
|
||||
</div>
|
||||
<div className="docedit-uppercol-divider"></div>
|
||||
</div>
|
||||
<div className="docedit-header-col3">
|
||||
<div className="scroll-button-box">
|
||||
<Scrollbutton onClick={onScrollClick} className="scroll-button">
|
||||
Scroll <ArrowUp /> <ArrowDown />
|
||||
</Scrollbutton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewYorkCaption;
|
||||
37
src/Components/Features/FeaturesPage.js
Normal file
37
src/Components/Features/FeaturesPage.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useState, useContext, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
FeaturesTop,
|
||||
FeaturesBottom,
|
||||
} from "../../Constants/Copy/featuresCopy.js";
|
||||
import legalTek from "../../Assets/Images/legalTek.jpg";
|
||||
import legalTechFlip from "../../Assets/Images/legalTechFlip.jpg";
|
||||
import { ReactComponent as HowToInfoSvg } from "../../Assets/svg/howToInfo.svg";
|
||||
import "../../styles/features.scss";
|
||||
|
||||
const FeaturesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="features-container">
|
||||
<div className="features-top-wrapper">
|
||||
<div className="features-top-left">
|
||||
<FeaturesTop />
|
||||
</div>
|
||||
<div className="features-top-right">
|
||||
<img className="features-image-one" src={legalTechFlip} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="features-bottom-wrapper">
|
||||
<div className="features-bottom-right">
|
||||
<img className="features-image-one" src={legalTek} />
|
||||
</div>
|
||||
<div className="features-bottom-left">
|
||||
<FeaturesBottom />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeaturesPage;
|
||||
47
src/Components/Footer/Footer.js
Normal file
47
src/Components/Footer/Footer.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import logoWhite from "../../Assets/logoWhite.png";
|
||||
|
||||
import "../../styles/footer.scss";
|
||||
const Footer = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
function handleClick(route) {
|
||||
navigate(`/${route}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="footer-container">
|
||||
<div className="footer-upper-box">
|
||||
<div className="footer-left-box">
|
||||
<div className="footer-left-inner">
|
||||
<img className="footer-logo" src={logoWhite}></img>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer-right-box">
|
||||
<a className="footer-item-link" onClick={() => handleClick("tos")}>
|
||||
Terms of Service
|
||||
</a>
|
||||
<a
|
||||
className="footer-item-link"
|
||||
onClick={() => handleClick("accessibility")}
|
||||
>
|
||||
Accessibility
|
||||
</a>
|
||||
<a
|
||||
className="footer-item-link"
|
||||
onClick={() => handleClick("privacy")}
|
||||
>
|
||||
Privacy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer-lower-box">
|
||||
<p className="footer-item">
|
||||
© 2023-2024 Novodraft - All Rights Reserved
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
127
src/Components/Home/DemoRequestPage.js
Normal file
127
src/Components/Home/DemoRequestPage.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import { useState } from "react";
|
||||
import Button from "../../pageElements/Button";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import Form from "react-bootstrap/Form";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import { demofields } from "../../Constants/Fields/DemoFields";
|
||||
import { splitEvery } from "../../Utils/Array";
|
||||
import TextInput from "../../pageElements/TextInput";
|
||||
import { objectMap } from "../../Utils/Object";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import {
|
||||
getFormDataDefaults,
|
||||
getValidatedFormData,
|
||||
handleFormDataChange,
|
||||
isFormDataHasErrors,
|
||||
} from "../../Utils/Form";
|
||||
import { db } from "../../firebase";
|
||||
import { collection, setDoc, doc } from "firebase/firestore";
|
||||
import "../../styles/demo-request-page.scss";
|
||||
|
||||
const DemoRequestPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [data, setData] = useState(getFormDataDefaults(demofields));
|
||||
const [isBusy, setIsBusy] = useState();
|
||||
const fieldsChunkSize = 2;
|
||||
|
||||
const handleChangeInput = (e, name) => {
|
||||
const newData = handleFormDataChange(e, name, data, demofields);
|
||||
if (newData !== null) {
|
||||
setData(newData);
|
||||
}
|
||||
};
|
||||
|
||||
const validateData = () => {
|
||||
const newData = getValidatedFormData(data, demofields);
|
||||
const hasErrors = isFormDataHasErrors(newData);
|
||||
setData(newData);
|
||||
return hasErrors ? null : objectMap(({ value }) => value, newData);
|
||||
};
|
||||
|
||||
async function saveUserData(dataValues) {
|
||||
console.log("saveUserData dataValues", dataValues);
|
||||
const { firstName, lastName, firm, telephone, email } = dataValues;
|
||||
const requestDate = new Date();
|
||||
dataValues["requestDate"] = requestDate;
|
||||
const requestId = uuidv4();
|
||||
try {
|
||||
const demoRef = collection(db, "demorequests");
|
||||
await setDoc(doc(demoRef, requestId), dataValues);
|
||||
} catch (error) {
|
||||
console.log(`Error saving request data to db: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRequestDemo() {
|
||||
console.log("handlerequest demo");
|
||||
const dataValues = validateData();
|
||||
if (dataValues === null) {
|
||||
return;
|
||||
}
|
||||
saveUserData(dataValues);
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="signup-super-container">
|
||||
<div className="signup-sub-container">
|
||||
<div className="signup-header">
|
||||
<h2 className="signup-header-text">
|
||||
Revolutionize Your Practice: Request A Demo
|
||||
</h2>
|
||||
<p className="requestdemo-para">
|
||||
Enter your information below we will contact you to schedule
|
||||
</p>
|
||||
</div>
|
||||
<Form className="demo-request-form">
|
||||
{splitEvery(Object.keys(demofields), fieldsChunkSize).map(
|
||||
(names, j) => (
|
||||
<Row key={`row${j}`}>
|
||||
{names.map((name, i) => (
|
||||
<Col
|
||||
key={`${name}${i + fieldsChunkSize * j}`}
|
||||
className="mb-3"
|
||||
>
|
||||
<TextInput
|
||||
name={name}
|
||||
value={data[name].value}
|
||||
onChange={(e) => handleChangeInput(e, name)}
|
||||
error={data[name].error}
|
||||
message={data[name].message}
|
||||
label={
|
||||
data[name].value.length === 0
|
||||
? demofields[name].label
|
||||
: ""
|
||||
}
|
||||
type={demofields[name].type}
|
||||
values={demofields[name].values}
|
||||
disabled={isBusy}
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)
|
||||
)}
|
||||
</Form>
|
||||
<div className="signup-button-box">
|
||||
<Button
|
||||
className="primary-button"
|
||||
type="button"
|
||||
size="lg"
|
||||
onClick={handleRequestDemo}
|
||||
disabled={isBusy}
|
||||
labelText="Request Demo"
|
||||
/>
|
||||
<div className="mt-3">
|
||||
<span>
|
||||
<Link to="/signup">Go to signup</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DemoRequestPage;
|
||||
250
src/Components/Home/HomePage.js
Normal file
250
src/Components/Home/HomePage.js
Normal file
@@ -0,0 +1,250 @@
|
||||
import Button from "../../pageElements/Button";
|
||||
import suitAI from "../../Assets/Images/suitAI.png";
|
||||
import laptop from "../../Assets/Images/laptop.png";
|
||||
import arrow from "../../Assets/Images/Arrow.png";
|
||||
import scales_centered from "../../Assets/Images/scales_centered.png";
|
||||
import hand_clear from "../../Assets/Images/hand_clear.png";
|
||||
import hand_rework6 from "../../Assets/Images/hand_rework6.png";
|
||||
import gavel_clear from "../../Assets/Images/gavel_clear.png";
|
||||
import Group80 from "../../Assets/Images/Group80.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "../../styles/homepage.scss";
|
||||
|
||||
const HomePage = () => {
|
||||
const navigate = useNavigate();
|
||||
const handleNavigate = () => {
|
||||
navigate("/signup");
|
||||
};
|
||||
const handleLogin = () => {
|
||||
navigate("/login");
|
||||
};
|
||||
const handleDemo = () => {
|
||||
navigate("/demorequestpage");
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className="homepage-container">
|
||||
<section
|
||||
className="section-1"
|
||||
style={{ backgroundImage: `url(${suitAI})`, backgroundSize: "cover" }}
|
||||
>
|
||||
<div className="first-section-subcontainer">
|
||||
<div className="row upper-row-one">
|
||||
<div className="col-md-12">
|
||||
<div className="heading-one-container">
|
||||
<h1 className="heading-1">
|
||||
Novodraft: Leverage AI to draft discovery faster <i>and </i>{" "}
|
||||
smarter.
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-block-1">
|
||||
Responses ready to serve in minutes.
|
||||
</p>
|
||||
<p className="text-block-two">
|
||||
Reclaim hours every week. Boost your billables or spend more
|
||||
time growing your practice.
|
||||
</p>
|
||||
<div className="try-it-button-box">
|
||||
<Button
|
||||
className="primary-button homepage-button"
|
||||
labelText="Try It For Free"
|
||||
onClick={handleNavigate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* Section 2 */}
|
||||
|
||||
<section className="section-2">
|
||||
<div className="home-row">
|
||||
<div className="home-col-50">
|
||||
<div className="heading-2-mobile-box">
|
||||
<h2 className="heading-2-mobile">
|
||||
Made by Attorneys, for Attorneys
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="pad-2">
|
||||
<img
|
||||
src={laptop}
|
||||
alt="image of a laptop computer"
|
||||
className="img-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="home-col-50">
|
||||
<div className="pad-2">
|
||||
<h2 className="heading-2">Made by Attorneys, for Attorneys</h2>
|
||||
<div className="text-block-2-box">
|
||||
<p className="text-block-2">
|
||||
Legal tech built by people that get it.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="section-two-body">
|
||||
Novodraft understands the demands on your time. For example,
|
||||
that discovery request on your desk. With a few clicks, and
|
||||
Novodraft's AI assistant on the task, you can draft a
|
||||
ready-to-serve response in minutes. It's as easy as uploading
|
||||
a .pdf file.
|
||||
</p>
|
||||
<p className="section-two-body">
|
||||
Novodraft cuts through a mire of rote tasks, but does much
|
||||
more than automate.
|
||||
</p>
|
||||
<p className="section-two-body">
|
||||
IntelliDraft AI technology powers persuasive arguments and
|
||||
boosts bland, boilerplate objections. Configurable for level
|
||||
of authoritative citation and for infering factual context
|
||||
from pleadings and briefs. Use it to augment your drafting as
|
||||
much or as little as you prefer.
|
||||
</p>
|
||||
<div className="home-link-box">
|
||||
<a className="link">
|
||||
Explore Novodraft
|
||||
<img src={arrow} className="img-1" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* Section 3 */}
|
||||
<section className="section-3">
|
||||
<div className="section-three-header-box">
|
||||
<h2 className="section-three-header">
|
||||
AI-Powered Legal Drafting Technology{" "}
|
||||
</h2>
|
||||
<p className="section-three-subhead">
|
||||
Created by litigators with law-centric AI at its core to give you
|
||||
an edge.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card-section-container">
|
||||
{/*********** CARD ONE *********** */}
|
||||
|
||||
<div className="home-card">
|
||||
<div className="card-image-container">
|
||||
<img src={gavel_clear} alt="" className="card-image" />
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="card-upper-box">
|
||||
<div className="home-card-title">
|
||||
<h3 className="foo-card-title">Proactive Alerts</h3>
|
||||
<div className="home-card-divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="home-card-text-box">
|
||||
<p className="card-texts">
|
||||
Your NovoDash displays important deadlines, sends email/SMS
|
||||
alerts, and will auto-draft responses and remind you to
|
||||
serve them.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*********** CARD TWO *********** */}
|
||||
|
||||
<div className="home-card">
|
||||
<div className="card-image-container">
|
||||
<img
|
||||
src={scales_centered}
|
||||
alt=""
|
||||
width="100%"
|
||||
className="card-image"
|
||||
/>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="card-upper-box">
|
||||
<div className="home-card-title">
|
||||
<h3 className="foo-card-title">Discovery Responses</h3>
|
||||
<div className="home-card-divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="home-card-text-box">
|
||||
<p className="card-texts">
|
||||
Novodraft's AI drafts responses complete with compelling
|
||||
arguments about what you choose not to disclose. Finished in
|
||||
minutes with a .docx file for service.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*********** CARD THREE *********** */}
|
||||
|
||||
<div className="home-card">
|
||||
<div className="card-image-container">
|
||||
<img
|
||||
src={hand_rework6}
|
||||
alt=""
|
||||
width="100%"
|
||||
className="card-image"
|
||||
/>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="card-upper-box">
|
||||
<div className="home-card-title">
|
||||
<h3 className="foo-card-title">Discovery Requests</h3>
|
||||
<div className="home-card-divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="home-card-text-box">
|
||||
<p className="card-texts">
|
||||
Generate discovery requests with a few clicks, or select
|
||||
manually from a comprehensive request library. Done in a
|
||||
heartbeat, with a .docx for review and service.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="section-5 py-5">
|
||||
<div
|
||||
className="home-test-container"
|
||||
style={{
|
||||
backgroundImage: `url(${Group80})`,
|
||||
backgroundSize: "cover",
|
||||
}}
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<div className="bottom-header-box">
|
||||
<h4 className="heading-6">
|
||||
Questions about our products and services? We’re here to
|
||||
answer!
|
||||
</h4>
|
||||
<h4 className="heading-6-mobile">
|
||||
Questions about our products and services?
|
||||
</h4>
|
||||
<h4 className="heading-6-mobile">We’re here to answer!</h4>
|
||||
</div>
|
||||
<h6 className="foo-heading">
|
||||
Let us show you how Novodraft will revolutionize your
|
||||
practice. Join our growing base of satisfied Novodrafters.
|
||||
</h6>
|
||||
</div>
|
||||
<div className="col-md-6 d-flex">
|
||||
<button className="button-2" onClick={handleDemo}>
|
||||
Request a demo
|
||||
</button>
|
||||
<button className="button-2" onClick={handleLogin}>
|
||||
Go to product login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
29
src/Components/HowTo/HowToPage.js
Normal file
29
src/Components/HowTo/HowToPage.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useContext } from "react";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext";
|
||||
import { Steps } from "../../Constants/Copy/howToSteps.js";
|
||||
import "../../styles/howTo-page.scss";
|
||||
import { ReactComponent as HowToInfoSvg } from "../../Assets/svg/howToInfo.svg";
|
||||
|
||||
const HowToPage = () => {
|
||||
const { appState } = useContext(AppContext);
|
||||
const { group } = appState;
|
||||
|
||||
if (!group) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="how-to-info-container">
|
||||
<div className="how-to-info-wrapper">
|
||||
<div className="how-to-image-wrapper">
|
||||
<HowToInfoSvg className="how-to-info-svg" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="how-to-steps-container">
|
||||
<Steps />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HowToPage;
|
||||
11
src/Components/Layout.js
Normal file
11
src/Components/Layout.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
|
||||
const Layout = () => {
|
||||
return (
|
||||
<div className="layout-container">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
129
src/Components/Login/Login.js
Normal file
129
src/Components/Login/Login.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { useState } from "react";
|
||||
import Button from "../../pageElements/Button";
|
||||
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
|
||||
import { Link, useNavigate, createSearchParams } from "react-router-dom";
|
||||
import "../../styles/login.scss";
|
||||
|
||||
const Login = () => {
|
||||
const navigate = useNavigate();
|
||||
const auth = getAuth();
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [notice, setNotice] = useState("");
|
||||
//const [searchParams, setSearchParams] = useSearchParams();
|
||||
const userLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
if (isBusy) {
|
||||
return;
|
||||
}
|
||||
setIsBusy(true);
|
||||
setNotice("");
|
||||
try {
|
||||
const userCredential = await signInWithEmailAndPassword(
|
||||
auth,
|
||||
email,
|
||||
password
|
||||
);
|
||||
// Signed in
|
||||
userCredential?.user && navigate("/dashboard");
|
||||
} catch (error) {
|
||||
if (
|
||||
["auth/invalid-login-credentials", "auth/invalid-email"].includes(
|
||||
error.code
|
||||
)
|
||||
) {
|
||||
setNotice("Incorrect email or password.");
|
||||
} else {
|
||||
setNotice(`Error occured (${error.code}).`);
|
||||
}
|
||||
} finally {
|
||||
setIsBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const mode = "enterEmail";
|
||||
|
||||
function handleClick(e) {
|
||||
e.preventDefault();
|
||||
console.log("handleClick fired");
|
||||
//searchParams.set("mode", mode);
|
||||
//setSearchParams(searchParams);
|
||||
//navigate(`/passwordreset/?mode=enterEmail`);
|
||||
navigate({
|
||||
pathname: "/passwordreset/",
|
||||
search: `?${createSearchParams({
|
||||
mode: "enterEmail",
|
||||
})}`,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login-container">
|
||||
<div className="login-form-wrapper">
|
||||
<form className="login-form">
|
||||
<div className="login-header">
|
||||
<h2 className="login-header-text">Login to Novodraft</h2>
|
||||
</div>
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
type="email"
|
||||
className="form-control"
|
||||
id="emailInput"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
disabled={isBusy}
|
||||
></input>
|
||||
<label htmlFor="emailInput" className="form-label">
|
||||
Email
|
||||
</label>
|
||||
</div>
|
||||
<div className="password-input-container">
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="passwordInput"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={isBusy}
|
||||
></input>
|
||||
<label htmlFor="passwordInput" className="form-label">
|
||||
Password
|
||||
</label>
|
||||
<button className="recover-link" onClick={handleClick}>
|
||||
Forgot password?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="alert-box">
|
||||
{"" !== notice && (
|
||||
<div className="login-alert" role="alert">
|
||||
{notice}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="login-button-wrapper">
|
||||
<Button
|
||||
className="primary-button"
|
||||
onClick={(e) => userLogin(e)}
|
||||
labelText="Submit"
|
||||
disabled={isBusy}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3 text-center">
|
||||
<div className="register-box">
|
||||
<Link className="create-link" to="/signup">
|
||||
Create an account.
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
35
src/Components/Modals/ConfirmModal.js
Normal file
35
src/Components/Modals/ConfirmModal.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import Button from "../../pageElements/Button";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
|
||||
const ConfirmModal = ({
|
||||
onCancel,
|
||||
onConfirm,
|
||||
buttonLabelText,
|
||||
modalText,
|
||||
isBusy,
|
||||
}) => {
|
||||
return (
|
||||
<Modal show="true" onHide={onCancel} size="lg">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Confirmation</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>{modalText}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
disabled={isBusy}
|
||||
className="secondary-button"
|
||||
onClick={onCancel}
|
||||
labelText="Cancel"
|
||||
/>
|
||||
<Button
|
||||
disabled={isBusy}
|
||||
className="primary-button"
|
||||
onClick={onConfirm}
|
||||
labelText={buttonLabelText}
|
||||
/>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmModal;
|
||||
561
src/Components/Modals/CreateModal.js
Normal file
561
src/Components/Modals/CreateModal.js
Normal file
@@ -0,0 +1,561 @@
|
||||
import { useState, useContext } from "react";
|
||||
import Button from "../../pageElements/Button";
|
||||
import Form from "react-bootstrap/Form";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import TextInput from "../../pageElements/TextInput";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import Radiogroup from "../../pageElements/Radiogroup";
|
||||
import { collection, setDoc, updateDoc, doc } from "firebase/firestore";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext.js";
|
||||
import { db } from "../../firebase";
|
||||
import { objectMap } from "../../Utils/Object";
|
||||
import "../../styles/modals.scss";
|
||||
import {
|
||||
getFormDataDefaults,
|
||||
getValidatedFormData,
|
||||
handleFormDataChange,
|
||||
isFormDataHasErrors,
|
||||
} from "../../Utils/Form";
|
||||
import "../../styles/modals.scss";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const CreateModal = ({ setShowModal, caseData }) => {
|
||||
const { appState } = useContext(AppContext);
|
||||
const navigate = useNavigate();
|
||||
const { group } = appState;
|
||||
const appUserId = group.appUserId;
|
||||
const editCaseId = caseData?.id;
|
||||
const isEditing = typeof editCaseId !== "undefined";
|
||||
|
||||
const fields = {
|
||||
caption: { required: true, maxLength: 45 },
|
||||
captionTwo: { required: true, maxLength: 45 },
|
||||
caseNumber: { required: true, maxLength: 45 },
|
||||
judge: { required: true, maxLength: 45 },
|
||||
plaintiffParties: { required: true, maxLength: 45 },
|
||||
defendantParties: { required: true, maxLength: 45 },
|
||||
jurisdiction: { required: true, maxLength: 45 },
|
||||
billingCode: { required: true, maxLength: 45 },
|
||||
venue: { required: true, maxLength: 45 },
|
||||
caseType: { required: true, maxLength: 45 },
|
||||
trialDate: { required: true, maxLength: 45, type: "date" },
|
||||
filedDate: { required: true, maxLength: 45, type: "date" },
|
||||
contactFirstName: { required: true, maxLength: 45 },
|
||||
contactLastName: { required: true, maxLength: 45 },
|
||||
contactEmail: { required: true, maxLength: 45, type: "email" },
|
||||
leadAttorneys: { required: true, maxLength: 45 },
|
||||
lawFirm: { required: false, maxLength: 45 },
|
||||
clientPosition: { required: true, default: "Defendant" },
|
||||
};
|
||||
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [data, setData] = useState(
|
||||
getFormDataDefaults(fields, isEditing ? caseData : undefined)
|
||||
);
|
||||
|
||||
const handleChangeInput = (e, name) => {
|
||||
const newData = handleFormDataChange(e, name, data, fields);
|
||||
if (newData !== null) {
|
||||
setData(newData);
|
||||
}
|
||||
};
|
||||
|
||||
const validateData = () => {
|
||||
const newData = getValidatedFormData(data, fields);
|
||||
const hasErrors = isFormDataHasErrors(newData);
|
||||
console.log("newData", newData);
|
||||
console.log("hasErrors", hasErrors);
|
||||
setData(newData);
|
||||
return hasErrors ? null : objectMap(({ value }) => value, newData);
|
||||
};
|
||||
|
||||
async function saveCaseData() {
|
||||
const dataValues = validateData();
|
||||
|
||||
if (dataValues === null) {
|
||||
return;
|
||||
}
|
||||
setIsBusy(true);
|
||||
if (isEditing) {
|
||||
try {
|
||||
await updateDoc(doc(db, "cases", editCaseId), dataValues);
|
||||
setShowModal(false);
|
||||
} catch (err) {
|
||||
console.log("Error updating case:", err);
|
||||
}
|
||||
} else {
|
||||
const caseId = uuidv4();
|
||||
const ownerId = appUserId; // userId;
|
||||
const caseData = {
|
||||
ownerId,
|
||||
caseId,
|
||||
...dataValues,
|
||||
};
|
||||
try {
|
||||
const casesRef = collection(db, "cases");
|
||||
await setDoc(doc(casesRef, `${caseId}`), caseData);
|
||||
setShowModal(false);
|
||||
} catch (err) {
|
||||
console.log("Error creating case:", err);
|
||||
}
|
||||
}
|
||||
setIsBusy(false);
|
||||
}
|
||||
|
||||
const stateOfCase = "NY";
|
||||
|
||||
const handleClose = () => {
|
||||
if (!isBusy) {
|
||||
setShowModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
if (!isBusy) {
|
||||
void saveCaseData();
|
||||
}
|
||||
navigate("/dashboard");
|
||||
};
|
||||
|
||||
const radioOptions = [
|
||||
{ name: "Plaintiff", label: "Plaintiff", value: "Plaintiff" },
|
||||
{ name: "Defendant", label: "Defendant", value: "Defendant" },
|
||||
];
|
||||
|
||||
const modalInputForm = (
|
||||
<>
|
||||
<p className="create-modal-header">
|
||||
Please enter case information carefully. It will be used later when
|
||||
generating new documents.
|
||||
</p>
|
||||
<Form>
|
||||
<Row className="modal-caption-row">
|
||||
<Col style={{ marginBottom: "0px" }}>
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Case caption</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div className="caption-one-container">
|
||||
<TextInput
|
||||
inputClassName={"create-modal-input"}
|
||||
key={"caption"}
|
||||
name={"caption"}
|
||||
value={data.caption.value}
|
||||
error={data.caption.error}
|
||||
message={data.caption.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "caption")}
|
||||
label={
|
||||
data.caption.value.length === 0
|
||||
? isEditing
|
||||
? caseData.caption
|
||||
: "Caption - Plaintiff(s)"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<div className="modal-caption-vee"> - v. -</div>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? <span className="edit-modal-label"></span> : <></>}
|
||||
<TextInput
|
||||
key={"captionTwo"}
|
||||
name={"captionTwo"}
|
||||
value={data.captionTwo.value}
|
||||
error={data.captionTwo.error}
|
||||
message={data.captionTwo.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "captionTwo")}
|
||||
label={
|
||||
data.captionTwo.value.length === 0
|
||||
? isEditing
|
||||
? caseData.captionTwo
|
||||
: "Caption - Defendant(s)"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Index number</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"caseNumber"}
|
||||
name={"caseNumber"}
|
||||
value={data.caseNumber.value}
|
||||
error={data.caseNumber.error}
|
||||
message={data.caseNumber.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "caseNumber")}
|
||||
label={
|
||||
data.caseNumber.value.length === 0
|
||||
? isEditing
|
||||
? caseData.caseNumber
|
||||
: stateOfCase === "NY"
|
||||
? "Index Number"
|
||||
: "Case Number"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? <div className="edit-modal-label">Venue</div> : <></>}
|
||||
<TextInput
|
||||
key={"venue"}
|
||||
name={"venue"}
|
||||
value={data.venue.value}
|
||||
error={data.venue.error}
|
||||
message={data.venue.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "venue")}
|
||||
label={
|
||||
data.venue.value.length === 0
|
||||
? isEditing
|
||||
? caseData.venue
|
||||
: "Venue"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? <div className="edit-modal-label">Judge</div> : <></>}
|
||||
<TextInput
|
||||
key={"judge"}
|
||||
name={"judge"}
|
||||
value={data.judge.value}
|
||||
error={data.judge.error}
|
||||
message={data.judge.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "judge")}
|
||||
label={
|
||||
data.judge.value.length === 0
|
||||
? isEditing
|
||||
? caseData.judge
|
||||
: "Judge"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Jurisdiction</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"jurisdiction"}
|
||||
name={"jurisdiction"}
|
||||
value={data.jurisdiction.value}
|
||||
error={data.jurisdiction.error}
|
||||
message={data.jurisdiction.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "jurisdiction")}
|
||||
label={
|
||||
data.jurisdiction.value.length === 0
|
||||
? isEditing
|
||||
? caseData.jurisdiction
|
||||
: "Jurisdiction"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Trial Date</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"trialDate"}
|
||||
name={"trialDate"}
|
||||
value={data.trialDate.value}
|
||||
error={data.trialDate.error}
|
||||
message={data.trialDate.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "trialDate")}
|
||||
label={
|
||||
data.trialDate.value.length === 0
|
||||
? isEditing
|
||||
? caseData.trialDate
|
||||
: "Trial Date"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Date filed</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"filedDate"}
|
||||
name={"filedDate"}
|
||||
value={data.filedDate.value}
|
||||
error={data.filedDate.error}
|
||||
message={data.filedDate.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "filedDate")}
|
||||
label={
|
||||
data.filedDate.value.length === 0
|
||||
? isEditing
|
||||
? caseData.filedDate
|
||||
: "Date Filed"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Defendant(s)</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"defendantParties"}
|
||||
name={"defendantParties"}
|
||||
value={data.defendantParties.value}
|
||||
error={data.defendantParties.error}
|
||||
message={data.defendantParties.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "defendantParties")}
|
||||
label={
|
||||
data.defendantParties.value.length === 0
|
||||
? isEditing
|
||||
? caseData.defendantParties
|
||||
: "Defendant Parties"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Plaintiff(s)</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"plaintiffParties"}
|
||||
name={"plaintiffParties"}
|
||||
value={data.plaintiffParties.value}
|
||||
error={data.plaintiffParties.error}
|
||||
message={data.plaintiffParties.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "plaintiffParties")}
|
||||
label={
|
||||
data.plaintiffParties.value.length === 0
|
||||
? isEditing
|
||||
? caseData.plaintiffParties
|
||||
: "Plaintiff Parties"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Contast first name</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"contactFirstName"}
|
||||
name={"contactFirstName"}
|
||||
value={data.contactFirstName.value}
|
||||
error={data.contactFirstName.error}
|
||||
message={data.contactFirstName.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "contactFirstName")}
|
||||
label={
|
||||
data.contactFirstName.value.length === 0
|
||||
? isEditing
|
||||
? caseData.contactFirstName
|
||||
: "Contact First Name"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Contact last name</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"contactLastName"}
|
||||
name={"contactLastName"}
|
||||
value={data.contactLastName.value}
|
||||
error={data.contactLastName.error}
|
||||
message={data.contactLastName.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "contactLastName")}
|
||||
label={
|
||||
data.contactLastName.value.length === 0
|
||||
? isEditing
|
||||
? caseData.contactLastName
|
||||
: "Contact Last Name"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Case type</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"caseType"}
|
||||
name={"caseType"}
|
||||
value={data.caseType.value}
|
||||
error={data.caseType.error}
|
||||
message={data.caseType.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "caseType")}
|
||||
label={
|
||||
data.caseType.value.length === 0
|
||||
? isEditing
|
||||
? caseData.caseType
|
||||
: "Case Type"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Contact email</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"contactEmail"}
|
||||
name={"contactEmail"}
|
||||
value={data.contactEmail.value}
|
||||
error={data.contactEmail.error}
|
||||
message={data.contactEmail.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "contactEmail")}
|
||||
label={
|
||||
data.contactEmail.value.length === 0
|
||||
? isEditing
|
||||
? caseData.contactEmail
|
||||
: "Contact Email"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<div className="radio-group-wrapper">
|
||||
<Radiogroup
|
||||
title="Client position"
|
||||
options={radioOptions}
|
||||
value={data.clientPosition.value}
|
||||
disabled={isBusy}
|
||||
onClick={(e) => handleChangeInput(e, "clientPosition")}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Billing code</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"billingCode"}
|
||||
name={"billingCode"}
|
||||
value={data.billingCode.value}
|
||||
error={data.billingCode.error}
|
||||
message={data.billingCode.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "billingCode")}
|
||||
label={
|
||||
data.billingCode.value.length === 0
|
||||
? isEditing
|
||||
? caseData.billingCode
|
||||
: "Billing Code"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
{isEditing ? (
|
||||
<div className="edit-modal-label">Lead attorney</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextInput
|
||||
key={"leadAttorneys"}
|
||||
name={"leadAttorneys"}
|
||||
value={data.leadAttorneys.value}
|
||||
error={data.leadAttorneys.error}
|
||||
message={data.leadAttorneys.message}
|
||||
disabled={isBusy}
|
||||
onChange={(e) => handleChangeInput(e, "leadAttorneys")}
|
||||
label={
|
||||
data.leadAttorneys.value.length === 0
|
||||
? isEditing
|
||||
? caseData.leadAttorneys
|
||||
: "Lead Attornery(s)"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col></Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal show="true" onHide={handleClose} size="lg">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
{isEditing ? "Edit case information" : "Enter case information"}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>{modalInputForm}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
className="secondary-button"
|
||||
disabled={isBusy}
|
||||
onClick={handleClose}
|
||||
labelText="Cancel"
|
||||
/>
|
||||
<Button
|
||||
className="primary-button"
|
||||
disabled={isBusy}
|
||||
onClick={handleCreate}
|
||||
labelText="Save Case"
|
||||
/>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateModal;
|
||||
260
src/Components/Modals/UploadModal.js
Normal file
260
src/Components/Modals/UploadModal.js
Normal file
@@ -0,0 +1,260 @@
|
||||
import React, { useState, useContext } from "react";
|
||||
import { FileUploader } from "react-drag-drop-files";
|
||||
import { AppContext } from "../../Hooks/useContext/appContext";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import Button from "../../pageElements/Button";
|
||||
import Form from "react-bootstrap/Form";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { collection, setDoc, doc } from "firebase/firestore";
|
||||
import { db } from "../../firebase";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import TextInput from "../../pageElements/TextInput";
|
||||
import Radio from "../../pageElements/Radio";
|
||||
import "../../styles/modals.scss";
|
||||
|
||||
const MAX_LENGTH_TITLE = 27;
|
||||
const MAX_LENGTH_DESC = 35;
|
||||
const MAX_LENGTH_DATE = 20;
|
||||
|
||||
const UploadModal = (props) => {
|
||||
const { appState } = useContext(AppContext);
|
||||
const { group } = appState;
|
||||
const appUserId = group.appUserId;
|
||||
const { setShowModal, caseData, handleSuccess } = props;
|
||||
const { caseId, caseNumber, jurisdiction, caption, captionTwo } = caseData;
|
||||
const [docTitle, setDocTitle] = useState("");
|
||||
const [docDescription, setDocDescription] = useState("");
|
||||
const [dateServed, setDateServed] = useState("");
|
||||
const [fileToUpload, setFileToUpload] = useState();
|
||||
const [fileToParse, setFileToParse] = useState("");
|
||||
const [fileChanged, setFileChanged] = useState();
|
||||
const [showFileDetails, setShowFileDetails] = useState("");
|
||||
const [titleError, setShowTitleError] = useState("");
|
||||
const [docError, setShowDocError] = useState("");
|
||||
const fileTypes = ["PDF"];
|
||||
const fileName = fileToParse ? fileToParse.name : undefined;
|
||||
const baseUrl = "http://localhost:4000";
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function saveToDb(uuidName) {
|
||||
const data = {
|
||||
ownerId: appUserId,
|
||||
documentName: `${uuidName}.pdf`,
|
||||
docDescription: docDescription,
|
||||
dateServed: dateServed,
|
||||
docTitle: docTitle,
|
||||
parentCaseId: caseId,
|
||||
parentCaseName: `${caption} v. ${captionTwo}`,
|
||||
parentCaseNumber: caseNumber,
|
||||
parentCaseJurisdiction: jurisdiction,
|
||||
responseGenerations: 0,
|
||||
};
|
||||
|
||||
try {
|
||||
const documentsRef = collection(db, "documents");
|
||||
await setDoc(doc(documentsRef, `${uuidName}`), data);
|
||||
} catch (err) {
|
||||
console.log("Error saving case to db:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileChange(file) {
|
||||
setFileToParse(file);
|
||||
setFileChanged(true);
|
||||
}
|
||||
|
||||
async function uploadFile(file) {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/uploadToParse`, {
|
||||
method: "POST",
|
||||
body: file,
|
||||
});
|
||||
const res = response;
|
||||
console.log("-------------------------------------------->res", res);
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function processFile() {
|
||||
const uuidName = uuidv4();
|
||||
const newName = `${uuidName}.pdf`;
|
||||
const title = docTitle;
|
||||
const description = docDescription || "";
|
||||
const formData = new FormData();
|
||||
|
||||
if (fileToParse && fileChanged) {
|
||||
formData.append("file", fileToParse[0], newName);
|
||||
}
|
||||
|
||||
if (fileName) {
|
||||
formData.append("fileName", fileName);
|
||||
}
|
||||
|
||||
formData.append("title", title);
|
||||
formData.append("description", description);
|
||||
setFileToUpload(formData);
|
||||
|
||||
let response = {};
|
||||
|
||||
const res = await uploadFile(formData);
|
||||
console.log("--------------------------res", res);
|
||||
response["uuidName"] = uuidName;
|
||||
response["res"] = res;
|
||||
return response;
|
||||
}
|
||||
|
||||
async function handleUpload() {
|
||||
if (!docTitle && !fileToParse) {
|
||||
setShowDocError("Please select a document to upload.");
|
||||
setShowTitleError("Please enter a document title");
|
||||
return;
|
||||
} else if (!docTitle) {
|
||||
setShowTitleError("Please enter a document title");
|
||||
return;
|
||||
} else if (!fileToParse) {
|
||||
setShowDocError("Please select a document to upload.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await processFile();
|
||||
if (response.res?.status !== 200) {
|
||||
saveToDb(response.uuidName);
|
||||
return;
|
||||
} else {
|
||||
saveToDb(response.uuidName);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
}
|
||||
|
||||
handleSuccess();
|
||||
}
|
||||
|
||||
const handleUploadCancel = () => {
|
||||
setShowFileDetails(false);
|
||||
setFileToParse(null);
|
||||
setFileChanged(false);
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
const handleTitleInput = (e) => {
|
||||
if (e.target.value.length <= MAX_LENGTH_TITLE) {
|
||||
setDocTitle(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDescInput = (e) => {
|
||||
if (e.target.value.length <= MAX_LENGTH_DESC) {
|
||||
setDocDescription(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDateInput = (e) => {
|
||||
if (e.target.value.length <= MAX_LENGTH_DATE) {
|
||||
setDateServed(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show="true" onHide={handleUploadCancel} size="lg">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Document Upload</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="modal-header-text-wrapper">
|
||||
<p className="upload-modal-header-text">
|
||||
Select a document in pdf format for upload. Enter the document title
|
||||
below. Document description is optional.
|
||||
</p>
|
||||
<p className="upload-modal-header-text">
|
||||
This document will be associated with {caption} v. {captionTwo}
|
||||
{caseNumber ? `, ${caseNumber}` : ""}.
|
||||
</p>
|
||||
<p className="upload-modal-header-text">
|
||||
If that is not the case you want to associate with this document,
|
||||
click "cancel", go back and select the intended case from your
|
||||
active case list.
|
||||
</p>
|
||||
</div>
|
||||
<Form>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
<TextInput
|
||||
key={"title"}
|
||||
name={"title"}
|
||||
value={docTitle}
|
||||
onChange={handleTitleInput}
|
||||
label={docTitle ? "" : "Title"}
|
||||
error={!!titleError}
|
||||
message={titleError}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="mb-3">
|
||||
<TextInput
|
||||
key={"description"}
|
||||
name={"description"}
|
||||
value={docDescription}
|
||||
onChange={handleDescInput}
|
||||
label="Description"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
<TextInput
|
||||
key={"date"}
|
||||
name={"date"}
|
||||
value={dateServed}
|
||||
onChange={handleDateInput}
|
||||
label={dateServed ? "" : "Date Served"}
|
||||
/>
|
||||
</Col>
|
||||
<Col />
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
<Form.Label>Select Document</Form.Label>
|
||||
<div className="file-uploader">
|
||||
<FileUploader
|
||||
multiple={true}
|
||||
handleChange={handleFileChange}
|
||||
name="file"
|
||||
>
|
||||
<div className={fileToParse ? "drag-box filled" : "drag-box"}>
|
||||
{fileToParse
|
||||
? fileToParse[0]?.name
|
||||
: "Click to select a file or drag and drop here"}
|
||||
</div>
|
||||
</FileUploader>
|
||||
</div>
|
||||
</Col>
|
||||
{docError ? (
|
||||
<div className="modal-document-error">{docError}</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
className="btn primary-button cancel-button"
|
||||
onClick={handleUploadCancel}
|
||||
labelText="Cancel"
|
||||
/>
|
||||
<Button
|
||||
labelText="Upload"
|
||||
className="primary-button pr-1 pl-1"
|
||||
onClick={handleUpload}
|
||||
/>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadModal;
|
||||
120
src/Components/Navigation/Navbar.js
Normal file
120
src/Components/Navigation/Navbar.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import React, { useState } from "react";
|
||||
import Nav from "react-bootstrap/Nav";
|
||||
import NavItem from "react-bootstrap/NavItem";
|
||||
import Navbar from "react-bootstrap/Navbar";
|
||||
import Dropdown from "react-bootstrap/Dropdown";
|
||||
import { List } from "react-bootstrap-icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SignOutUser } from "../../firebase";
|
||||
import Navpanel from "./Navpanel";
|
||||
import logoNobackCropped from "../../Assets/logoNobackCropped.png";
|
||||
|
||||
const CustomToggle = React.forwardRef(({ onClick }, ref) => (
|
||||
<div className="navbar-gear">
|
||||
<List
|
||||
className="desktop-hamburg"
|
||||
size="36px"
|
||||
ref={ref}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick(e);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
||||
const NavbarElement = (props) => {
|
||||
//const [showMenu, setShowMenu] = useState(false);
|
||||
const [menuOpen, setMenuOpen] = useState(true);
|
||||
const token = window.sessionStorage.getItem("token");
|
||||
const navigate = useNavigate();
|
||||
const navItems = token
|
||||
? [
|
||||
{ text: "Dashboard", link: `dashboard` },
|
||||
{ text: "Cases", link: "cases" },
|
||||
{ text: "Documents", link: "documents" },
|
||||
{ text: "How-to", link: "how-to" },
|
||||
{ text: "Blog", link: "dashboard" },
|
||||
]
|
||||
: [
|
||||
{ text: "Login", link: "login" },
|
||||
{ text: "Features", link: "features" },
|
||||
{ text: "Signup", link: "signup" },
|
||||
{ text: "Blog", link: "login" },
|
||||
];
|
||||
|
||||
function navToggle() {
|
||||
setMenuOpen(!menuOpen);
|
||||
}
|
||||
|
||||
function handleClick(item) {
|
||||
const link = item.toLowerCase();
|
||||
navigate(`/${link}`);
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
SignOutUser();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="navbar-wrapper">
|
||||
<Navbar fixed="top" className="navbar-main">
|
||||
<div className="logo-container">
|
||||
<a href="/">
|
||||
<img src={logoNobackCropped} className="nav-"></img>
|
||||
</a>
|
||||
</div>
|
||||
<Nav className="me-auto nav-row navbar-items">
|
||||
{navItems.map((item, i) => (
|
||||
<NavItem key={`item${i}`} href={item.link}>
|
||||
<button
|
||||
className="nav-button"
|
||||
onClick={() => handleClick(item.link)}
|
||||
>
|
||||
{item.text}
|
||||
</button>
|
||||
</NavItem>
|
||||
))}
|
||||
</Nav>
|
||||
<div className="navbar-user-info">
|
||||
{token ? (
|
||||
<div>
|
||||
<div className="nav-menu-container">
|
||||
<Dropdown drop="down" align="end">
|
||||
<Dropdown.Toggle as={CustomToggle} />
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item eventKey="2" href="/account">
|
||||
Account
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item eventKey="4" onClick={() => handleLogout()}>
|
||||
Logout
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<div className="navbar-mobile-hamburg">
|
||||
<List size="36px" onClick={() => navToggle()} />
|
||||
<Navpanel menuOpen={menuOpen} />
|
||||
</div>
|
||||
</Navbar>
|
||||
<div className="navbar-main-spacer"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavbarElement;
|
||||
|
||||
/*
|
||||
<button
|
||||
className="nav-button"
|
||||
onClick={() => handleClick(item.text)}
|
||||
>
|
||||
{item.text}
|
||||
</button>
|
||||
*/
|
||||
49
src/Components/Navigation/Navpanel.js
Normal file
49
src/Components/Navigation/Navpanel.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import "../../styles/navpanel.scss";
|
||||
import "../../styles/contact.scss";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function Navpanel(props) {
|
||||
const { menuOpen, setMenuOpen, navToggle } = props;
|
||||
console.log("+++++++++menuOpen", menuOpen);
|
||||
const mobileMainHeadingClass = menuOpen
|
||||
? "mobileMainHeadingShow"
|
||||
: "mobile-main-heading-hide";
|
||||
|
||||
const mobileHeadingWrapperClass = menuOpen
|
||||
? "mobile-heading-wrapper1"
|
||||
: "mobile-heading-wrapper";
|
||||
|
||||
console.log("mobileMainHeadingClass", mobileMainHeadingClass);
|
||||
console.log("mobileHeadingWrapperClass", mobileHeadingWrapperClass);
|
||||
return (
|
||||
<div className={mobileMainHeadingClass}>
|
||||
<div className={mobileHeadingWrapperClass}>
|
||||
<div className="mobileHeadingClose">
|
||||
<div className="mobileHeadingCloseInner">
|
||||
<a className="mobileHeadingButton" onClick={navToggle}></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mobileHeadingLinkWrap">
|
||||
<div className="mobile-info-box">
|
||||
<div className="contact-detail-one">
|
||||
<Link className="contactLink" href="/login">
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
<div className="contact-detail-two">
|
||||
<a className="contact-link-two" href="/features">
|
||||
Features
|
||||
</a>
|
||||
</div>
|
||||
<div className="contact-detail-three">
|
||||
<a className="nav-link-three" href="/signup">
|
||||
Signup
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
src/Components/Privacy.js
Normal file
36
src/Components/Privacy.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const Privacy = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tos-container">
|
||||
<h1>Privacy</h1>
|
||||
<p className="tos-para">
|
||||
Novodraft is committed to protecting the privacy of our users.
|
||||
Novodraft does not sell or rent user information and we do not share
|
||||
user information without prior consent except as compelled by law.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Novodraft collects only enough information from users to enable the
|
||||
functioning of this website and its services. It will never collect
|
||||
extra information about you and uses no tracking, cookies or logging
|
||||
with personal information.{" "}
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
The only information ever stored is your chosen email address and
|
||||
contact information that is provided upon signup. NovoDraft is located
|
||||
within the United States, and therefore will process and store your
|
||||
information in the United States.{" "}
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
If you would like to remove your data, or if you have any questions
|
||||
about how your data is used, please contact us.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Privacy;
|
||||
10
src/Components/PrivateRoute.js
Normal file
10
src/Components/PrivateRoute.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import React, { useContext } from "react";
|
||||
import { AuthContext } from "../Context/AuthProvider";
|
||||
import { Navigate } from "react-router-dom";
|
||||
|
||||
const PrivateRoute = ({ children }) => {
|
||||
const { currentUserEmail } = useContext(AuthContext);
|
||||
return currentUserEmail === null ? <Navigate to="/" /> : children;
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
||||
14
src/Components/ScrollToTop.js
Normal file
14
src/Components/ScrollToTop.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export const ScrollToTop = () => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ScrollToTop;
|
||||
43
src/Components/SettingsPanel/SettingsPanel.js
Normal file
43
src/Components/SettingsPanel/SettingsPanel.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import Button from "react-bootstrap/Button";
|
||||
|
||||
const SettingsPanel = (props) => {
|
||||
const { menuOpen, setMenuOpen } = props;
|
||||
const mobileMainHeadingClass = menuOpen
|
||||
? "mobile-main-heading1"
|
||||
: "mobile-main-heading";
|
||||
const mobileHeadingWrapperClass = menuOpen
|
||||
? "mobile-heading-wrapper1"
|
||||
: "mobile-heading-wrapper";
|
||||
|
||||
return (
|
||||
<div className={mobileMainHeadingClass}>
|
||||
<div className={mobileHeadingWrapperClass}>
|
||||
<div className="mobile-heading-link-wrap">
|
||||
<div className="mobile-heading-links">
|
||||
<div className="mobile-heading-link-box">
|
||||
<a className="mobile-heading-link">Features</a>
|
||||
</div>
|
||||
<div className="mobile-heading-link-box">
|
||||
<a className="mobile-heading-link">Pricing</a>
|
||||
</div>
|
||||
<div className="mobile-heading-link-box">
|
||||
<a className="mobile-heading-link">Resources</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mobile-lower">
|
||||
<div className="mobile-border-div" />
|
||||
<div className="mobile-lower">
|
||||
<a className="mobile-heading-link">Login</a>
|
||||
</div>
|
||||
<div className="mobile-heading-link-box">
|
||||
<Button>Sign Up</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPanel;
|
||||
215
src/Components/SignupPage/SignupPage.js
Normal file
215
src/Components/SignupPage/SignupPage.js
Normal file
@@ -0,0 +1,215 @@
|
||||
import React, { useState } from "react";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import Form from "react-bootstrap/Form";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import Button from "../../pageElements/Button";
|
||||
import { db, auth } from "../../firebase";
|
||||
import { createUserWithEmailAndPassword } from "firebase/auth";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import TextInput from "../../pageElements/TextInput";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { collection, setDoc, doc } from "firebase/firestore";
|
||||
import { splitEvery } from "../../Utils/Array";
|
||||
import {
|
||||
getFormDataDefaults,
|
||||
getValidatedFormData,
|
||||
handleFormDataChange,
|
||||
isFormDataHasErrors,
|
||||
} from "../../Utils/Form";
|
||||
import { objectMap } from "../../Utils/Object";
|
||||
import { signupfields } from "../../Constants/Fields/Signupfields";
|
||||
import "../../styles/signup.scss";
|
||||
|
||||
const SignupPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [notice, setNotice] = useState("");
|
||||
|
||||
const fieldsChunkSize = 2;
|
||||
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [data, setData] = useState(getFormDataDefaults(signupfields));
|
||||
|
||||
const handleChangeInput = (e, name) => {
|
||||
const newData = handleFormDataChange(e, name, data, signupfields);
|
||||
if (newData !== null) {
|
||||
setData(newData);
|
||||
}
|
||||
};
|
||||
|
||||
const validateData = () => {
|
||||
const newData = getValidatedFormData(data, signupfields);
|
||||
const hasErrors = isFormDataHasErrors(newData);
|
||||
setData(newData);
|
||||
return hasErrors ? null : objectMap(({ value }) => value, newData);
|
||||
};
|
||||
|
||||
async function saveUserData(authId, dataValues) {
|
||||
const appUserId = uuidv4();
|
||||
const firmId = uuidv4();
|
||||
const fbAuthUid = authId;
|
||||
const {
|
||||
firstName,
|
||||
lastName,
|
||||
firm,
|
||||
telephone,
|
||||
streetAddress,
|
||||
city,
|
||||
state,
|
||||
zipCode,
|
||||
barNumber,
|
||||
practiceArea,
|
||||
email,
|
||||
} = dataValues;
|
||||
|
||||
const userData = {
|
||||
appUserId,
|
||||
fbAuthUid,
|
||||
firmId,
|
||||
firstName,
|
||||
lastName,
|
||||
firm,
|
||||
telephone,
|
||||
streetAddress,
|
||||
city,
|
||||
state,
|
||||
zipCode,
|
||||
barNumber,
|
||||
practiceArea,
|
||||
email,
|
||||
};
|
||||
|
||||
try {
|
||||
const usersRef = collection(db, "users");
|
||||
await setDoc(doc(usersRef, fbAuthUid), userData);
|
||||
} catch (error) {
|
||||
console.log(`Error saving new user to db: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSignup = async (e) => {
|
||||
e.preventDefault();
|
||||
if (isBusy) {
|
||||
return;
|
||||
}
|
||||
const dataValues = validateData();
|
||||
if (dataValues === null) {
|
||||
return;
|
||||
}
|
||||
setIsBusy(true);
|
||||
try {
|
||||
const userCredential = await createUserWithEmailAndPassword(
|
||||
auth,
|
||||
dataValues.email,
|
||||
dataValues.password
|
||||
);
|
||||
const user = userCredential.user;
|
||||
await saveUserData(user.uid, dataValues);
|
||||
navigate("/");
|
||||
} catch (error) {
|
||||
setIsBusy(false);
|
||||
setNotice(
|
||||
error.message || "Sorry, something went wrong. Please try again."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="signup-super-container">
|
||||
<div className="signup-sub-container">
|
||||
<div className="signup-header">
|
||||
<h1 className="signup-header-text">Create a Novodraft account</h1>
|
||||
</div>
|
||||
<Form className="signup-form">
|
||||
{splitEvery(
|
||||
Object.keys(signupfields).slice(0, -2),
|
||||
fieldsChunkSize
|
||||
).map((names, j) => (
|
||||
<Row key={`row${j}`}>
|
||||
{names.map((name, i) => (
|
||||
<Col key={`${name}${i + fieldsChunkSize * j}`} className="mb-3">
|
||||
<TextInput
|
||||
name={name}
|
||||
value={data[name].value}
|
||||
onChange={(e) => handleChangeInput(e, name)}
|
||||
error={data[name].error}
|
||||
message={data[name].message}
|
||||
label={
|
||||
data[name].value.length === 0
|
||||
? signupfields[name].label
|
||||
: ""
|
||||
}
|
||||
type={signupfields[name].type}
|
||||
values={signupfields[name].values}
|
||||
disabled={isBusy}
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
))}
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
<TextInput
|
||||
id="signupPassword"
|
||||
type="password"
|
||||
label={
|
||||
data.password.value.length === 0
|
||||
? signupfields.password.label
|
||||
: ""
|
||||
}
|
||||
value={data.password.value}
|
||||
error={data.password.error}
|
||||
message={data.password.message}
|
||||
onChange={(e) => handleChangeInput(e, "password")}
|
||||
disabled={isBusy}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="mb-3">
|
||||
<TextInput
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
label={
|
||||
data.confirmPassword.value.length === 0
|
||||
? signupfields.confirmPassword.label
|
||||
: ""
|
||||
}
|
||||
value={data.confirmPassword.value}
|
||||
error={data.confirmPassword.error}
|
||||
message={data.confirmPassword.message}
|
||||
onChange={(e) => handleChangeInput(e, "confirmPassword")}
|
||||
disabled={isBusy}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
<div className="signup-button-box">
|
||||
<Button
|
||||
className="primary-button"
|
||||
type="button"
|
||||
size="lg"
|
||||
onClick={handleSignup}
|
||||
disabled={isBusy}
|
||||
labelText="Create Account"
|
||||
/>
|
||||
<div className="mt-3">
|
||||
<span>
|
||||
<Link to="/">Return to login</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{"" !== notice && (
|
||||
<>
|
||||
<br></br>
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{notice}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignupPage;
|
||||
169
src/Components/TermsOfService.js
Normal file
169
src/Components/TermsOfService.js
Normal file
@@ -0,0 +1,169 @@
|
||||
const TermsOfService = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="tos-container">
|
||||
<h1>Terms of Service And Use</h1>
|
||||
<p className="tos-para">
|
||||
YOU SHOULD READ THESE TERMS OF SERVICE AND USE BEFORE USING THIS WEB
|
||||
SITE.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
By using the Website, you acknoweldge that you have read this, and
|
||||
that you understand it. The Website which is located at the domain
|
||||
novodraft.com and novodraft.ai ("Novodraft") and/or any mobile apps
|
||||
available for download (collectively, the “Web Site," “Websites” and
|
||||
"Apps") are governed by these Terms of Service and Use (the
|
||||
“Agreement,” “Terms of Service", or "TOS"). Any reference herein to
|
||||
the Website, Websites, and/or Apps is intended, and shall be
|
||||
construed, to pertain to one, all, or any of them, indivuidually and
|
||||
collectively.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
By using the Web Site and Apps, you (“you” or “User”) signify your
|
||||
acceptance of this Agreement and your acknowledgement that all
|
||||
information that you provide, directly or indirectly, through the Web
|
||||
Sites and App will be managed in accordance with the Privacy Notice.
|
||||
IF YOU DO NOT ACCEPT THESE TERMS OF SERVICE AND USE, YOU ARE NOT
|
||||
AUTHORIZED TO ACCESS OR USE THE WEB SITE OR APPS.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Novodraft.com{" "}
|
||||
<u>
|
||||
IS NOT INTENDED AS A SUBSTITUTE FOR THE SKILL, KNOWLEDGE, EXPERTISE
|
||||
OR WORK PRODUCT OF A PROFESSIONAL, LICENSED ATTORNEY.
|
||||
</u>
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Novodraft is not a law firm. Use of the Web Site does not create an
|
||||
attorney-client relationship. The Web Site should not be used as a
|
||||
substitute for an attorney or as creating an attorney-client
|
||||
relationship, for example, including but not limited to, any
|
||||
reresentation by an attorney, the advice of an attorney, or an
|
||||
attorney's work product.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Novodraft and this Website are intended for use by Licensed Attorneys,
|
||||
as an aid to, or supplement to, the preparation of documents.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Any Attorney using Novodraft by accessing this Web Site acknoweldges
|
||||
that he/she will review and edit, amend or otherwise alter materials
|
||||
produced by using the Website as required by law, professional
|
||||
prudence and ethical duties.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Communications. You agree that by providing your contact information,
|
||||
you consent to receiving communication, in connection with your
|
||||
membership. This may include communication about your account,
|
||||
features, and services via e-email, push notification, phone, or text
|
||||
message (including by an automatic telephone dialing system and/or
|
||||
with an artificial or pre-recorded voice) at any of the phone numbers
|
||||
provided by you or on your behalf. Standard text messaging charges
|
||||
applied by your cell phone carrier may apply.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
User Eligibility. The Websites should only be accessed and used by
|
||||
individuals who agree to be bound by these Terms of Use and who are at
|
||||
least 18 years of age. The Websites may be accessible worldwide;
|
||||
however, the Websites are intended for use only in the USA and Canada.
|
||||
If you access/use the Websites from outside the USA or Canada, you do
|
||||
so at your own risk and are responsible for complying with the laws
|
||||
and regulations of the territory from which you access/use the
|
||||
Websites.
|
||||
</p>
|
||||
<p>
|
||||
Intellectual property rights. The Web Site and the information,
|
||||
computer code, and related functionality appearing, featured or
|
||||
otherwise displayed on the Websites are owned by Novodraft, its
|
||||
affiliates, and their respective licensors or other third parties and
|
||||
protected under the copyright, trademark and other laws of the United
|
||||
States and other countries sand international treaty provisions.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Limited license. Novodraft grants to you a limited, non-exclusive,
|
||||
non-transferable license to use the Web Site in strict accordance with
|
||||
these Terms of Service and Use and the instructions provided by us on
|
||||
the Web Site. The materials provided on the Web Site, including,
|
||||
without limitation, the Information, computer code, and related
|
||||
functionality, are for your personal use only. Except as may be
|
||||
explicitly permitted through the Websites, you may not copy, modify,
|
||||
upload, republish, distribute, display, post, license, create
|
||||
derivative works from, or transmit anything you obtain from the Web
|
||||
Sites, including anything you download from the Websites, unless you
|
||||
first obtain our written consent.{" "}
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Any rights not expressly granted herein are reserved to Novodraft and
|
||||
its affiliates. You may not remove, obscure, or otherwise deface
|
||||
proprietary notices appearing on the Web Site, or any content or
|
||||
Information. Any unauthorized use of the Websites or its contents may
|
||||
violate copyright laws, trademark laws, the laws of privacy and
|
||||
publicity and communications regulations and statutes.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Restrictions on Use. As a condition of your use of the Websites, you
|
||||
warrant that you will not use the Websites for any purpose that is
|
||||
unlawful or prohibited by these terms, conditions and notices. You may
|
||||
not use the Websites in any way that could damage, disable, overburden
|
||||
or impair the Websites or interfere with any other party’s use and
|
||||
enjoyment of the Websites. You may not obtain or attempt to obtain any
|
||||
materials or information through any means not intentionally made
|
||||
available or provided for through the Web Site.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Revocation of privileges. You agree that your use of the Websites may
|
||||
be suspended or terminated immediately upon receipt of any notice
|
||||
which alleges that you have used the Websites in violation of these
|
||||
Terms of Use and/or for any purpose that violates any local, state,
|
||||
federal or law of the USA or other jurisdictions, including, but not
|
||||
limited to, the posting of information that may violate third party
|
||||
rights, may defame a third party, may be obscene or pornographic, may
|
||||
harass or assault others, or may violate any laws, rules or
|
||||
regulations, including, hacking or other criminal regulations. You
|
||||
understand that actions in violation of these Terms of Use may subject
|
||||
you to serious civil and criminal legal penalties and Novodraft
|
||||
reserves the right to pursue penalties and other remedies to the
|
||||
fullest extent of the law to protect our rights.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
No Warranties. Novodraft MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT
|
||||
THE WEB SITE, THE SUITABILITY OF THE INFORMATION CONTAINED ON OR
|
||||
RECEIVED THROUGH THE WEB SITE, OR ANY SERVICES OR PRODUCTS RECEIVED
|
||||
THROUGH THE WEB SITE. ALL INFORMATION AND USE OF THE WEB SITE ARE
|
||||
PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND. Novodraft HEREBY
|
||||
EXPRESSLY DISCLAIMS ALL WARRANTIES WITH REGARD TO THE Websites, THE
|
||||
INFORMATION CONTAINED ON OR RECEIVED THROUGH USE OF THE Websites AND
|
||||
ANY SERVICES OR PRODUCTS RECEIVED THROUGH THE Websites, INCLUDING ALL
|
||||
EXPRESS, STATUTORY AND IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. Novodraft DOES
|
||||
NOT WARRANT THAT THE CONTENTS OR ANY INFORMATION RECEIVED THROUGH THE
|
||||
Websites ARE ACCURATE, RELIABLE OR CORRECT; THAT THE Websites WILL BE
|
||||
AVAILABLE AT ANY PARTICULAR TIME OR LOCATION; THAT ANY DEFECTS OR
|
||||
ERRORS WILL BE CORRECTED; OR THAT THE CONTENTS OR ANY INFORMATION
|
||||
RECEIVED THROUGH THE Websites ARE FREE OF VIRUSES OR OTHER DESTRUCTIVE
|
||||
OR HARMFUL COMPONENTS. YOUR USE OF THE Websites IS SOLELY AT YOUR OWN
|
||||
RISK. USER EXPRESSLY AGREES THAT IT HAS RELIED ON NO WARRANTIES,
|
||||
REPRESENTATIONS OR STATEMENTS OTHER THAN IN THIS AGREEMENT.
|
||||
</p>
|
||||
<p className="tos-para">
|
||||
Limitation of Liability. UNDER NO CIRCUMSTANCES SHALL Novodraft BE
|
||||
LIABLE FOR ANY DAMAGES, INCLUDING, WITHOUT LIMITATION, DIRECT,
|
||||
INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES OR
|
||||
LOST PROFITS THAT RESULT FROM, OR ARISE OUT OF OR IN CONNECTION WITH
|
||||
THE USE OF, OR INABILITY TO USE THE Websites, THE INFORMATION
|
||||
CONTAINED ON OR RECEIVED THROUGH USE OF THE Websites, OR ANY SERVICES
|
||||
OR PRODUCTS RECEIVED THROUGH THE Websites. THIS LIMITATION APPLIES
|
||||
WHETHER THE ALLEGED LIABILITY IS BASED ON CONTRACT, TORT, NEGLIGENCE,
|
||||
STRICT LIABILITY OR ANY OTHER BASIS, EVEN IF Novodraft HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME JURISDICTIONS
|
||||
DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES, Novodraft's LIABILITY IN SUCH JURISDICTIONS
|
||||
SHALL BE LIMITED TO THE MAXIMUM EXTENT PERMITTED BY THE LAW OF YOUR
|
||||
JURISDICTION.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsOfService;
|
||||
Reference in New Issue
Block a user