first commit

This commit is contained in:
Kenneth Jannette
2024-01-11 18:24:41 -06:00
commit 4c1fb67383
103 changed files with 29954 additions and 0 deletions

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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? Were here to
answer!
</h4>
<h4 className="heading-6-mobile">
Questions about our products and services?
</h4>
<h4 className="heading-6-mobile">Were 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;

View 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
View File

@@ -0,0 +1,11 @@
import { Outlet } from "react-router-dom";
const Layout = () => {
return (
<div className="layout-container">
<Outlet />
</div>
);
};
export default Layout;

View 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;

View 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;

View 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;

View 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;

View 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>
*/

View 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
View 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;

View 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;

View 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;

View 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;

View 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;

View 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 partys 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;