first commit for refactored project

This commit is contained in:
KS Jannette
2026-05-23 17:08:47 -04:00
commit 6545f8f549
166 changed files with 48911 additions and 0 deletions

164
ax3Client/src/App.js Normal file
View File

@@ -0,0 +1,164 @@
import React, { useContext, useState } from "react";
import { Routes, Route, useNavigate } from "react-router-dom";
import { getAuth } from "firebase/auth";
import Login from "./Components/Login/Login";
import DemoRequestPage from "./Components/Home/DemoRequestPage";
import Privacy from "./Components/Privacy";
import Accessibility from "./Components/Accessibility";
import SignupPage from "./Components/SignupPage/SignupPage";
import PasswordReset from "./Components/Account/PasswordReset";
import DocumentListPage from "./Components/Document/DocumentListPage";
import DocEditPage from "./Components/Document/DocEditPage";
import Homepage from "./Components/Home/HomePage";
import HomepageB from "./Components/Home/HomePageB";
import Dashboard from "./Components/Dashboard/Dashboard";
import OptoutPage from "./Components/OptoutPage";
import RequestEditPage from "./Components/Document/ReqEditPage";
import CaseListPage from "./Components/Case/CaseListPage";
import CaseDetailsPage from "./Components/Case/CaseDetailsPage";
import CaseTypePage from "./Components/Case/CaseTypePage";
import Footer from "./Components/Footer/Footer";
import Navbar from "./Components/Navigation/Navbar";
import AccountPage from "./Components/Account/AccountPage";
import SettingsPage from "./Components/Account/SettingsPage";
import TermsOfService from "./Components/TermsOfService";
import ScrollToTop from "./Components/ScrollToTop";
import PrivateRoute from "./Components/PrivateRoute";
import FeaturesPage from "./Components/Features/FeaturesPage";
import { AppContext } from "./Hooks/useContext/appContext";
import { AuthContext } from "./Context/AuthProvider";
import HowToPage from "./Components/HowTo/HowToPage";
function App() {
const [settingsOpen, toggleSettingsOpen] = useState();
const { appState } = useContext(AppContext);
const { currentUserEmail } = useContext(AuthContext);
const { group } = appState;
const navigate = useNavigate();
// wait for user information
if (typeof currentUserEmail === "undefined") {
return null;
}
return (
<>
<ScrollToTop />
<Navbar />
<div className="main-container">
<Routes>
<Route exact path="/" element={<HomepageB />} />
<Route exact path="/login" element={<Login />} />
<Route exact path="/optout" element={<OptoutPage />} />
<Route
path="/signup/:isUpgrade?/:currentPlan?"
element={<SignupPage />}
/>
<Route path="/requestpage/:supReq" element={<DemoRequestPage />} />
<Route
exact
path="/passwordreset/:mode?/:oobCode?"
element={<PasswordReset />}
/>
<Route exact path="/privacy" element={<Privacy />} />
<Route exact path="/accessibility" element={<Accessibility />} />
<Route exact path="/features" element={<FeaturesPage />} />
<Route exact path="/tos" element={<TermsOfService />} />
<Route
exact
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
<Route
exact
path="/cases"
element={
<PrivateRoute>
<CaseListPage perPage={10} />
</PrivateRoute>
}
/>
<Route
exact
path="/casetype/:caseId"
element={
<PrivateRoute>
<CaseTypePage />
</PrivateRoute>
}
/>
<Route
exact
path="/casedetails/:caseId"
element={
<PrivateRoute>
<CaseDetailsPage />
</PrivateRoute>
}
/>
<Route
exact
path="/documents/:docId?/:docType?/:caseId?"
element={
<PrivateRoute>
<DocumentListPage perPage={10} />
</PrivateRoute>
}
/>
<Route
exact
path="/how-to"
element={
<PrivateRoute>
<HowToPage />
</PrivateRoute>
}
/>
<Route
exact
path="/account"
element={
<PrivateRoute>
<AccountPage />
</PrivateRoute>
}
/>
<Route
exact
path="/settings"
element={
<PrivateRoute>
<SettingsPage />
</PrivateRoute>
}
/>
<Route
exact
path="/docedit/:documentId/:caseId/:docType/:clientPosition/:responseGenerations"
element={
<PrivateRoute>
<DocEditPage />
</PrivateRoute>
}
/>
<Route
exact
path="/reqedit/:documentId/:documentType/:caseId"
element={
<PrivateRoute>
<RequestEditPage />
</PrivateRoute>
}
/>
</Routes>
</div>
<Footer />
</>
);
}
export default App;

BIN
ax3Client/src/Assets/.DS_Store vendored Normal file

Binary file not shown.

BIN
ax3Client/src/Assets/Images/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" style="" data-bbox="7.846 146.79 1684.07 327.113">
<g transform="matrix(0.9549080729484558, 0, 0, 0.9763198494911194, 1366.5592041015625, 121.5036849975586)">
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 360 360" enable-background="new 0 0 360 360" width="360" height="360">
<path class="st0" d="M159.45 118.47c-0.59 0.1-1.19 0.17-1.79 0.18c-7.65 0.15-26.56 1.05-31.96 2.55c-5.4 1.5-8.85 0.75-18.31-1.35&#xA;&#x9;c-9.45-2.1-17.86 0.9-17.86 0.9s-8.33-4.8-9.53-6.98c-1.2-2.18-2.55-3.82-3.3-2.33c-0.75 1.5 0.75 5.03 0.9 5.4&#xA;&#x9;c0.15 0.38-3.75 0.38-8.7 0.53c-4.95 0.15-6.75 5.1-4.95 9.15c1.8 4.05 16.06 2.7 20.41 2.7c4.35 0 6.3-0.9 8.85-0.6&#xA;&#x9;c2.55 0.3 15.16 1.95 23.56 1.35c8.4-0.6 14.56 1.5 22.66 1.95c8.1 0.45 13.51-1.8 17.71 4.8c4.2 6.6 6.75 26.11 5.4 34.81&#xA;&#x9;c-1.35 8.7-7.5 18.46-4.8 37.36c2.7 18.91 3.6 25.66 1.65 41.87c-1.95 16.2-2.7 25.51 0.6 33.91c3.3 8.4 8.25 18.9 6.9 21.9&#xA;&#x9;c-1.35 3-2.1 8.4 0.3 8.71c2.4 0.3 15.38 1.65 14.11-2.4c-1.28-4.05-4.8-5.55-6.3-13.66c-1.5-8.1-1.35-24.76-1.95-28.81&#xA;&#x9;c-0.6-4.05-3.3-13.5-1.5-17.11c1.8-3.6 4.51-18.3 5.41-25.06c0.9-6.75 3.07-14.26 3.07-14.26s0 26.41 0 34.07&#xA;&#x9;c0 7.65-4.72 19.51-3.22 27.31c1.5 7.8 7.65 21.91 8.25 26.71c0.6 4.8-1.35 10.21 1.65 11.11c3 0.9 25.21 0.75 27.16 0.75&#xA;&#x9;c1.95 0-1.5-2.85-8.1-5.55c-6.6-2.7-14.71-3.75-14.11-18.46c0.6-14.71-0.75-29.41 0.15-32.11c0.9-2.7 2.55-8.25 2.55-12.46&#xA;&#x9;c0-3.59 4.94-20.04 6-41.26c0-17.71-2.02-27.21-2.85-32.57c-1.35-8.7 1.2-28.21 5.4-34.81s9.61-4.35 17.71-4.8&#xA;&#x9;c8.1-0.45 14.26-2.55 22.66-1.95c8.4 0.6 21.01-1.05 23.56-1.35c2.55-0.3 4.5 0.6 8.86 0.6c4.35 0 18.61 1.35 20.41-2.7&#xA;&#x9;c1.8-4.05 0-9-4.95-9.15c-4.95-0.15-8.86-0.15-8.71-0.53c0.15-0.38 1.65-3.9 0.9-5.4c-0.75-1.5-2.1 0.15-3.3 2.33&#xA;&#x9;c-1.2 2.18-9.53 6.98-9.53 6.98s-8.4-3-17.86-0.9c-9.45 2.1-12.9 2.85-18.31 1.35c-5.4-1.5-24.31-2.4-31.96-2.55&#xA;&#x9;c-0.6-0.01-1.2-0.07-1.79-0.18h-6.07h-0.01h-0.01h-0.03h-0.03c-0.78-0.01-6.96-0.13-6.96-2.06v-5.23c1.95-1.95 3.1-4.1 4.24-6.59&#xA;&#x9;c0 0 0.47 0.21 1.3 0.05c0.84-0.16 1.57-2.67 1.36-4.65c-0.21-1.99-1.72-1.57-1.72-1.57c0-8.54-2.04-15.79-12.64-15.79&#xA;&#x9;c-10.61 0-12.64 7.25-12.64 15.79c0 0-1.52-0.42-1.73 1.57c-0.21 1.99 0.52 4.5 1.36 4.65c0.84 0.16 1.3-0.05 1.3-0.05&#xA;&#x9;c1.14 2.49 2.28 4.64 4.24 6.59v5.23c0 1.93-6.18 2.05-6.95 2.06h-0.03h-0.02h-0.01h-0.01H159.45L159.45 118.47z" fill-rule="evenodd" clip-rule="evenodd" fill="#0196FF" />
<path class="st1" d="M129.4 115.77c-4.21-2.26-8.2-4.51-13.48-6.01c-8.11-2.3-19.4-8.14-21.69-9.28c-2.3-1.15-4.44-0.97-8.53-2.45&#xA;&#x9;c-4.09-1.48-17.96-5.06-18.27-9.48c-0.31-4.42 3.06-8.47 7.77-6.92c4.71 1.54 8.38 2.87 8.36 2.46c-0.01-0.4-0.22-4.23 0.99-5.38&#xA;&#x9;c1.22-1.16 1.93 0.86 2.31 3.31c0.39 2.45 6.59 9.8 6.59 9.8s8.92 0.04 17.1 5.23c8.18 5.19 11.17 7.07 16.76 7.5&#xA;&#x9;c5.59 0.43 23.67 6.01 30.92 8.48C158.23 113.03 140.23 114.54 129.4 115.77L129.4 115.77z M205.07 196.07c0 0 0.3 11.11-0.75 19.36&#xA;&#x9;c-1.05 8.25-3.6 17.11-3.6 17.11s9.01 15.45 9.9 20.41c0.9 4.95-0.9 9.45 8.11 19.51c9 10.05 14.25 15.91 15.31 18.46&#xA;&#x9;c1.05 2.55 3.15 12.15 5.7 10.95c2.55-1.2 18.01-8.4 19.96-11.55c1.95-3.15-1.42-2.77-5.33-1.57c-3.9 1.2-12.31 0.52-14.63-3.98&#xA;&#x9;c-2.32-4.5-4.95-17.63-10.95-27.99c-6-10.36-9.16-12.61-11.4-23.86c-2.25-11.25-1.2-10.35-5.4-22.21&#xA;&#x9;C207.77 198.85 205.07 196.07 205.07 196.07L205.07 196.07z M152.4 197.87c0 0-5.55 8.4-6.9 15.46c-1.35 7.05-2.1 8.85-3.6 12.61&#xA;&#x9;c-1.5 3.75-6.9 9.6-11.4 15.61c-4.5 6.01-10.35 14.71-11.85 21.91c-1.5 7.2-3.15 18.16-6 21.61c-2.85 3.45-7.81 3.9-8.86 5.25&#xA;&#x9;c-1.05 1.35 2.1 4.65 6.15 7.35c4.05 2.7 9.15 1.5 9.3-1.35c0.15-2.85 0.6-11.56 4.05-16.81c3.45-5.25 14.4-22.21 16.51-27.31&#xA;&#x9;c2.1-5.1 1.8-6.76 3.75-8.56c1.95-1.8 10.95-12.3 10.95-12.3s-1.2-14.71-2.1-20.26C151.5 205.52 152.4 197.87 152.4 197.87&#xA;&#x9;L152.4 197.87z M230.65 115.77c4.21-2.26 8.2-4.51 13.48-6.01c8.11-2.3 19.4-8.14 21.7-9.28c2.3-1.15 4.44-0.97 8.53-2.45&#xA;&#x9;c4.09-1.48 17.95-5.06 18.27-9.48c0.31-4.42-3.06-8.47-7.77-6.92c-4.71 1.54-8.38 2.87-8.37 2.46c0.01-0.4 0.22-4.23-0.99-5.38&#xA;&#x9;c-1.21-1.16-1.92 0.86-2.31 3.31c-0.39 2.45-6.59 9.8-6.59 9.8s-8.92 0.04-17.1 5.23c-8.18 5.19-11.17 7.07-16.75 7.5&#xA;&#x9;c-5.59 0.43-23.67 6.01-30.92 8.48C201.82 113.03 219.81 114.54 230.65 115.77L230.65 115.77z" fill-rule="evenodd" clip-rule="evenodd" fill="#0000FF" />
<path class="st2" d="M216.19 25.9c71.34 15.95 124.53 78.05 124.53 152.23c0 86.27-71.95 156.21-160.7 156.21&#xA;&#x9;c-88.75 0-160.7-69.93-160.7-156.21c0-74.18 53.2-136.28 124.53-152.23v2.7C78.79 44.77 30.57 103.57 30.57 173.64&#xA;&#x9;c0 82.54 66.92 149.46 149.46 149.46s149.46-66.92 149.46-149.46c0-70.07-48.22-128.87-113.29-145.04V25.9L216.19 25.9z" fill-rule="evenodd" clip-rule="evenodd" fill="#000066" />
<path class="st3" d="M282.06 122.02c-0.93 0-1.73 0.54-2.13 1.32l-46.8 1.38c-0.42-0.71-1.18-1.19-2.06-1.19&#xA;&#x9;c-0.93 0-1.72 0.53-2.12 1.3l-30.27 0.64c-0.49-1.29-1.73-2.22-3.2-2.22c-1.07 0-2.02 0.5-2.65 1.28l-10.5-3.7&#xA;&#x9;c-0.12-0.7-0.56-1.29-1.15-1.63V93.14c1.21-0.47 2.07-1.63 2.07-3c0-1.78-1.45-3.23-3.22-3.23c-1.78 0-3.23 1.44-3.23 3.23&#xA;&#x9;c0 1.37 0.86 2.54 2.07 3v26.05c-0.71 0.4-1.2 1.16-1.2 2.03c0 0 0 0.01 0 0.01l-11.04 6.27c-0.46-0.33-1.02-0.53-1.62-0.53&#xA;&#x9;c-0.89 0-1.67 0.42-2.18 1.07L148 124.5c-0.25-1.28-1.37-2.25-2.72-2.25c-1.12 0-2.08 0.67-2.52 1.62l-37.86-0.13&#xA;&#x9;c-0.44-0.96-1.4-1.64-2.53-1.64c-1.53 0-2.78 1.24-2.78 2.78c0 1.53 1.24 2.78 2.78 2.78c1.12 0 2.08-0.67 2.52-1.62l37.86 0.13&#xA;&#x9;c0.44 0.96 1.4 1.64 2.53 1.64c0.89 0 1.67-0.43 2.18-1.07l14.83 3.55c0.21 1.08 1.04 1.93 2.11 2.18l6.59 32.16&#xA;&#x9;c-0.52 0.44-0.85 1.09-0.85 1.82c0 0.83 0.42 1.57 1.07 2l-4.32 48.12c-1.48 0.38-2.58 1.71-2.58 3.31c0 1.42 0.87 2.64 2.1 3.16&#xA;&#x9;l-2.85 49.9c-0.81 0.39-1.37 1.21-1.37 2.16c0 1.11 0.76 2.03 1.78 2.31l8.06 32.93l2.23-0.54l-8.06-32.93&#xA;&#x9;c0.48-0.44 0.79-1.06 0.79-1.76c0-0.86-0.46-1.61-1.13-2.03l2.85-49.91c1.42-0.42 2.47-1.72 2.47-3.28c0-0.83-0.31-1.58-0.8-2.18&#xA;&#x9;l9.58-16.09c0.01 0 0.01 0 0.02 0l10.71 21.11c-0.27 0.39-0.42 0.85-0.42 1.36c0 0.73 0.33 1.37 0.84 1.81L183 263.44&#xA;&#x9;c-1.68 0.22-2.98 1.65-2.98 3.39c0 1.7 1.24 3.11 2.87 3.38l6.96 38.7l2.26-0.41l-6.96-38.7c1.03-0.59 1.73-1.69 1.73-2.97&#xA;&#x9;c0-1.23-0.65-2.3-1.63-2.91l8.06-37.57c0.99-0.3 1.72-1.2 1.72-2.29c0-0.97-0.58-1.8-1.41-2.18l-2.32-31.9&#xA;&#x9;c1.21-0.53 2.05-1.73 2.05-3.14c0-1.38-0.82-2.56-1.99-3.11l4.96-53.75c1.13-0.29 2.03-1.14 2.39-2.23l30.27-0.64&#xA;&#x9;c0.41 0.72 1.18 1.21 2.07 1.21c0.93 0 1.73-0.54 2.13-1.32l46.8-1.38c0.42 0.71 1.18 1.19 2.06 1.19c1.32 0 2.4-1.08 2.4-2.4&#xA;&#x9;C284.47 123.1 283.39 122.02 282.06 122.02z M192.04 126.68c0 0.41 0.09 0.81 0.22 1.17l-8.22 6.87c-0.81-0.67-1.79-1.14-2.87-1.34&#xA;&#x9;v-10.13c0.14-0.08 0.27-0.17 0.38-0.27L192.04 126.68z M178.87 123.26v10.13c-1.32 0.25-2.5 0.9-3.4 1.84l-7.73-5.06&#xA;&#x9;c0.02-0.14 0.04-0.27 0.04-0.41c0-0.08-0.02-0.16-0.02-0.24l11.06-6.28C178.84 123.24 178.85 123.25 178.87 123.26z M174.21 137.15&#xA;&#x9;c-0.31 0.75-0.49 1.57-0.49 2.43c0 2.49 1.44 4.63 3.53 5.65l-4.56 16.32l-6.01-29.33L174.21 137.15z M179.46 145.85&#xA;&#x9;c0.18 0.02 0.37 0.03 0.56 0.03c0.05 0 0.1-0.01 0.14-0.01l7.71 36.8l-13.12-15.38c0.1-0.27 0.16-0.55 0.16-0.85&#xA;&#x9;c0-0.67-0.28-1.28-0.73-1.72L179.46 145.85z M186.97 185.15c-0.29 0.5-0.47 1.08-0.47 1.7c0 0.66 0.2 1.27 0.52 1.79l-6.41 7.98&#xA;&#x9;l-5.92-25.88L186.97 185.15z M182.24 198.27l6.58-8.2c0.07 0.02 0.13 0.04 0.2 0.06l2.05 28.31L182 200.56&#xA;&#x9;c0.27-0.39 0.42-0.85 0.42-1.36C182.42 198.87 182.36 198.56 182.24 198.27z M182.42 145.41c2.29-0.94 3.9-3.2 3.9-5.83&#xA;&#x9;c0-1.12-0.3-2.17-0.81-3.09l8.23-6.87c0.1 0.06 0.19 0.11 0.3 0.16l-4.55 49.36L182.42 145.41z M173.02 173.75l5.41 23.66&#xA;&#x9;c-0.5 0.44-0.81 1.08-0.81 1.79c0 0.45 0.13 0.86 0.34 1.22l-8.65 14.53L173.02 173.75z" fill="#FFFFFF" />
</svg>
</g>
<path text="Novodraft" fill="#000066" space="preserve" text-anchor="middle" offset="0.5" font-scale="contain" font-size="48" font-family="Exo" transform="matrix(1.4588379859924316, 0, 0, 1.4588379859924316, 286.92626953125, 463.7492370605469)" data-fl-textpath="" font-weight="400" font-style="normal" letter-spacing="4" d="M-121.55 0L-121.55-35.33L-118.43-35.33L-100-6.58L-100-35.33L-96.21-35.33L-96.21 0L-99.33 0L-117.76-28.75L-117.76 0L-121.55 0ZM-75.316 0.29Q-79.246 0.29 -81.646-0.82Q-84.046-1.92 -85.226-4.78Q-86.396-7.63 -86.396-12.86L-86.396-12.86Q-86.396-18.14 -85.196-21Q-83.996-23.86 -81.646-24.91Q-79.296-25.97 -75.316-25.97L-75.316-25.97Q-71.376-25.97 -68.976-24.89Q-66.576-23.81 -65.376-20.95Q-64.176-18.1 -64.176-12.86L-64.176-12.86Q-64.176-7.63 -65.376-4.78Q-66.576-1.92 -68.956-0.82Q-71.326 0.29 -75.316 0.29L-75.316 0.29ZM-75.316-2.83Q-72.676-2.83 -71.156-3.53Q-69.646-4.22 -68.836-6.38Q-68.016-8.54 -68.016-12.86L-68.016-12.86Q-68.016-17.18 -68.806-19.32Q-69.596-21.46 -71.136-22.15Q-72.676-22.85 -75.316-22.85L-75.316-22.85Q-77.996-22.85 -79.516-22.15Q-81.026-21.46 -81.816-19.32Q-82.606-17.18 -82.606-12.86L-82.606-12.86Q-82.606-8.54 -81.816-6.38Q-81.026-4.22 -79.516-3.53Q-77.996-2.83 -75.316-2.83L-75.316-2.83ZM-47.84 0L-57.49-25.63L-53.6-25.63L-45.63-3.55L-37.42-25.63L-33.58-25.63L-43.52 0L-47.84 0ZM-15.812 0.29Q-19.742 0.29 -22.142-0.82Q-24.542-1.92 -25.722-4.78Q-26.892-7.63 -26.892-12.86L-26.892-12.86Q-26.892-18.14 -25.692-21Q-24.492-23.86 -22.142-24.91Q-19.792-25.97 -15.812-25.97L-15.812-25.97Q-11.872-25.97 -9.472-24.89Q-7.072-23.81 -5.872-20.95Q-4.672-18.1 -4.672-12.86L-4.672-12.86Q-4.672-7.63 -5.872-4.78Q-7.072-1.92 -9.452-0.82Q-11.822 0.29 -15.812 0.29L-15.812 0.29ZM-15.812-2.83Q-13.172-2.83 -11.652-3.53Q-10.142-4.22 -9.332-6.38Q-8.512-8.54 -8.512-12.86L-8.512-12.86Q-8.512-17.18 -9.302-19.32Q-10.092-21.46 -11.632-22.15Q-13.172-22.85 -15.812-22.85L-15.812-22.85Q-18.492-22.85 -20.012-22.15Q-21.522-21.46 -22.312-19.32Q-23.102-17.18 -23.102-12.86L-23.102-12.86Q-23.102-8.54 -22.312-6.38Q-21.522-4.22 -20.012-3.53Q-18.492-2.83 -15.812-2.83L-15.812-2.83ZM 14.014 0.29Q 9.264 0.29 7.034-2.83Q 4.804-5.95 4.804-12.62L 4.804-12.62Q 4.804-17.42 5.764-20.35Q 6.724-23.28 8.834-24.62Q 10.944-25.97 14.494-25.97L 14.494-25.97Q 18.144-25.97 22.364-24.43L 22.364-24.43L 22.364-36.62L 26.164-36.62L 26.164 0L 23.084 0L 22.364-2.59Q 21.214-1.58 18.674-0.65Q 16.124 0.29 14.014 0.29L 14.014 0.29ZM 15.124-2.83Q 17.134-2.83 18.844-3.43Q 20.544-4.03 22.364-4.99L 22.364-4.99L 22.364-21.74Q 20.114-22.42 18.694-22.7Q 17.284-22.99 15.504-22.99L 15.504-22.99Q 12.964-22.99 11.474-22.06Q 9.984-21.12 9.284-18.91Q 8.594-16.7 8.594-12.77L 8.594-12.77Q 8.594-7.39 10.084-5.11Q 11.564-2.83 15.124-2.83L 15.124-2.83ZM 37.314 0L 37.314-25.63L 40.194-25.63L 41.104-21.17Q 42.974-23.42 45.134-24.67Q 47.294-25.92 49.884-25.92L 49.884-25.92Q 51.184-25.92 52.284-25.68L 52.284-25.68L 52.284-21.79Q 50.124-22.03 49.504-22.03L 49.504-22.03Q 46.964-22.03 45.114-21.12Q 43.264-20.21 41.104-18.19L 41.104-18.19L 41.104 0L 37.314 0ZM 65.12 0.29Q 62.24 0.29 60.44-1.44Q 58.64-3.17 58.64-6.14L 58.64-6.14L 58.64-7.97Q 58.64-11.09 60.68-12.91Q 62.72-14.74 66.66-14.74L 66.66-14.74L 75.34-14.74L 75.34-17.9Q 75.34-20.5 73.93-21.74Q 72.51-22.99 68.72-22.99L 68.72-22.99L 60.75-22.99L 60.75-25.25Q 65.26-26.02 70.11-26.02L 70.11-26.02Q 74.77-26.02 76.95-24.12Q 79.14-22.22 79.14-18.1L 79.14-18.1L 79.14 0L 76.21 0L 75.49-2.93Q 75.15-2.64 73.4-1.82Q 71.65-1.01 69.39-0.36Q 67.14 0.29 65.12 0.29L 65.12 0.29ZM 66.8-2.54Q 68.1-2.54 69.97-3.02Q 71.84-3.5 73.35-4.03Q 74.86-4.56 75.34-4.75L 75.34-4.75L 75.34-12.82L 67.52-12.48Q 64.88-12.38 63.66-11.18Q 62.43-9.98 62.43-7.92L 62.43-7.92L 62.43-6.77Q 62.43-4.56 63.7-3.55Q 64.98-2.54 66.8-2.54L 66.8-2.54ZM 91.68 0L 91.68-22.51L 87.74-22.51L 87.74-24.96L 91.68-25.63L 91.68-28.56Q 91.68-32.83 93.34-34.87Q 94.99-36.91 99.02-36.91L 99.02-36.91Q 101.47-36.91 103.97-36.43L 103.97-36.43L 103.97-33.74L 99.7-33.74Q 97.25-33.74 96.36-32.57Q 95.47-31.39 95.47-28.75L 95.47-28.75L 95.47-25.63L 103.44-25.63L 103.44-22.51L 95.47-22.51L 95.47 0L 91.68 0ZM 120.444 0Q 116.654 0 114.884-1.94Q 113.104-3.89 113.204-8.3L 113.204-8.3L 113.344-22.51L 109.264-22.51L 109.264-24.96L 113.394-25.63L 114.014-32.78L 116.944-32.78L 116.944-25.63L 124.334-25.63L 124.334-22.51L 116.944-22.51L 116.944-8.3Q 116.944-5.38 118.044-4.3Q 119.154-3.22 120.834-3.07L 120.834-3.07L 123.954-2.78L 123.954 0L 120.444 0Z" />
<g transform="matrix(0.18786652386188507, 0, 0, 0.18786652386188507, -11.404075622558594, 345.9037170410156)">
<path d="m287.855469 166.050781c-25.496094-29.097656-47.390625-55.292969-47.390625-90.984375 0-18.597656 6.894531-36.296875 19.195312-50.09375 4.199219-4.5 4.796875-11.398437 2.101563-16.800781-3.003907-5.699219-9-8.695313-15.300781-8.0976562-13.195313 1.8007812-41.09375 22.5000002-65.988282 53.6914062-2.101562 2.402344-4.199218 4.800781-6 7.5-30.59375 40.195313-63.289062 107.484375-58.191406 200.171875-19.195312-23.097656-30.894531-60.894531-26.394531-94.488281.898437-7.5-3.902344-14.699219-11.398438-16.496094-7.5-1.800781-15 2.398437-17.699219 9.597656-8.996093 24.898438-18.894531 44.691407-28.195312 63.589844-17.398438 34.195313-32.09375 63.890625-32.09375 107.386719 0 99.285156 80.6875 180.972656 179.972656 180.972656s179.972656-81.6875 179.972656-180.972656c0-81.589844-38.691406-125.984375-72.589843-164.976563zm0 0" fill="#ff9100" transform="matrix(1.3307298421859741, 0, 0, 1.3307298421859741, 101.80066680908203, 0)" />
<path d="m360.445312 331.027344c0 99.285156-80.6875 180.972656-179.972656 180.972656v-458.234375c24.898438-31.191406 52.792969-51.890625 65.992188-53.6914062 6.296875-.5976568 12.296875 2.3984372 15.296875 8.0976562 2.699219 5.402344 2.097656 12.300781-2.101563 16.800781-12.296875 13.796875-19.195312 31.496094-19.195312 50.09375 0 35.691406 21.894531 61.886719 47.394531 90.984375 33.894531 38.992188 72.585937 83.386719 72.585937 164.976563zm0 0" fill="#ff641a" transform="matrix(1.3307298421859741, 0, 0, 1.3307298421859741, 101.80066680908203, 0)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

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,242 @@
import { useState, useContext, useEffect } from "react";
import { useNavigate } from "react-router-dom";
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";
import ConfirmModal from "../Modals/ConfirmModal";
const AccountPage = () => {
const navigate = useNavigate();
const { currentUserCreatedAt } = useContext(AuthContext);
const { appState } = useContext(AppContext);
const { group } = appState;
const [docsGenerated, setDocsGenerated] = useState(null);
const appUserId = group ? group.appUserId : null;
const [showModal, setShowModal] = useState(false);
const subscriptionPlan = group?.subscriptionPlan[0].label;
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
const docsAllowed = group?.docsAllowedPerMonth;
const handleConfirmUpgrade = () => {
const isUpgrade = true;
const currentPlan = group?.subscriptionPlan;
navigate(`/signup/${isUpgrade}/${currentPlan}`);
};
function getDocumentsCount() {
if (!appUserId) {
return;
}
return onSnapshot(
query(collection(db, "documents"), where("ownerId", "==", appUserId)),
(snapshot) => setDocsGenerated(snapshot.docs.length)
);
}
useEffect(getDocumentsCount, [appUserId]);
const handleCancelSub = async () => {
setShowModal(true);
try {
let response = await fetch(`${apiUrl}/cancel-subscription`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
appUserId: appUserId,
}),
});
} catch (err) {
console.log(err);
}
setShowModal(false);
};
const handleSupport = () => {
const supReq = "5ac45d12";
navigate(`/requestpage/${supReq}`);
};
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} 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 upgrade-account"
labelText="Upgrade to add more"
onClick={handleConfirmUpgrade}
/>
</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 style={{ marginTop: "8px" }}>
<a href="mailto:support@novodraft.ai">support@novodraft.ai</a>
</div>
<div style={{ marginTop: "4px" }} onClick={handleSupport}>
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 onClick={handleSupport}>
<a href="#">Billing</a>
</div>
<div>
<a href="/privacy">Privacy</a>
</div>
</div>
</div>
<div className="account-section">Active Subscriptions</div>
<div className="account-card account-subscriptions">
<div className="account-subscriptions-title">
{`${subscriptionPlan?.toUpperCase()} PLAN`}
</div>
<div className="account-subscriptions-item">
<div className="account-subscriptions-item-label"></div>
<div className="account-subscriptions-item-value"></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 cancel-sub-button"
labelText="Cancel Subscription"
onClick={() => setShowModal(true)}
/>
</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>{group?.firm}</div>
<div>
<a href={`mailto:${group?.email}`}>{group?.email}</a>
</div>
<div>{group?.telephone}</div>
</div>
</div>
{showModal ? (
<ConfirmModal
onCancel={() => setShowModal(false)}
onConfirm={handleCancelSub}
buttonLabelText="Confirm Cancel"
modalText="Are you sure? Your subscription will end at the conclusion of the current billing period."
/>
) : null}
</div>
);
};
export default AccountPage;

View File

@@ -0,0 +1,216 @@
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,312 @@
import React, { useEffect, useContext, useState } from "react";
import { useNavigate, Link } 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 ConfirmModal from "../Modals/ConfirmModal.js";
import { db } from "../../firebase";
import { useParams } from "react-router-dom";
import { AppContext } from "../../Hooks/useContext/appContext.js";
import LoadingSpinner from "../../pageElements/LoadingSpinner";
import { ArrowLeft } from "react-bootstrap-icons";
import {
doc,
getDoc,
updateDoc,
onSnapshot,
increment,
} from "firebase/firestore";
const CaseDetailsPage = () => {
const { caseId } = useParams();
const [subCase, setSubCase] = useState(null);
const navigate = useNavigate();
const [showUploadModal, setShowUploadModal] = useState();
const [uploadType, setUploadType] = useState();
const [showCreateModal, setShowCreateModal] = useState();
const [showConfirmModal, setShowConfirmModal] = useState();
const [isLoading, setIsLoading] = useState(false);
const [docsAllowed, setDocsAllowed] = useState(null);
const [docsGenerated, setDocsGenerated] = useState(null);
const [document, setDocument] = useState(null);
const { appState } = useContext(AppContext);
const { group } = appState;
const appUserId = group ? group.appUserId : null;
const fbUserId = group ? group.fbAuthUid : null;
const message =
"Parsing document. Please be patient, this may take several minutes.";
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
function getDocument(docId, caseId) {
const docRef = doc(db, "documents", `${docId}`);
const res = getDoc(docRef).then(function (doc) {
if (doc.exists) {
const data = doc.data();
if (data.docType != undefined) {
const caseId = data.parentCaseId;
const docType = data.docType;
const clientPosition = data.clientPosition;
if (docType != "combined-numbered") {
const responseGenerations = 1;
navigate(
`/docedit/${docId}/${caseId}/${docType}/${clientPosition}/${responseGenerations}`
);
} else if (docType == "combined-numbered") {
const responseGenerations = 0;
navigate(
`/docedit/${docId}/${caseId}/${docType}/${clientPosition}/${responseGenerations}`
);
}
} else {
setTimeout(getDocument, 30000, docId, caseId);
}
} else {
console.log("No such document.");
}
});
}
function handleIncomingDiscoveryRequest(docId, isComplaint, clientPosition) {
//ONLY TRIGGER FROM THIS SIDE IF IT IS COMBINED NUMBERED
setTimeout(generateRespIncomingDiscReq, 10000, docId, clientPosition);
setTimeout(getDocument, 50000, docId);
}
// isComplaint = true - NEVER CHANGE THIS/THESE ARGS
async function generateOutgoingDiscReq(docId, clientPosition) {
try {
const response = await fetch(
`${apiUrl}/v1/generate-outgoing-disc-req/${docId}/${clientPosition}`,
{
method: "POST",
}
);
const res = response;
return res;
} catch (err) {
console.log("generateOutgoingDiscReq out error", err);
}
}
// isComplaint = false - NEVER CHANGE THESE ARGS
async function generateRespIncomingDiscReq(docId, clientPosition) {
console.log(
"generateRespIncomingDiscReq ------------------------fired----------------->"
);
try {
const response = await fetch(
`${apiUrl}/v1/generate-disc-responses/${docId}/${clientPosition}`,
{
method: "POST",
}
);
const res = response;
return res;
} catch (err) {
console.log("generateRespIncomingDiscReq resp err", err);
}
}
async function handleSuccess(docId, docType, radioValue) {
setShowUploadModal(false);
const clientPosition = subCase.clientPosition;
if (docType === "complaint") {
const isComplaint = true;
generateOutgoingDiscReq(docId, isComplaint, clientPosition);
setTimeout(handleNavEdit, 60000, docId, caseId);
} else {
const isComplaint = false;
handleIncomingDiscoveryRequest(docId, isComplaint, clientPosition);
}
}
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]);
useEffect(() => {
const fetchData = async () => {
try {
const docRef = doc(db, "users", fbUserId);
const response = await getDoc(docRef).then(function (doc) {
if (doc.exists) {
const data = doc.data();
setDocsAllowed(data.docsAllowedPerMonth);
setDocsGenerated(data.docsGenerated);
return data;
} else {
console.log("No such document.");
}
});
} catch (err) {
console.error(err);
}
};
fetchData();
}, [fbUserId]);
const handleNavigate = (docId, caseId) => {
getDocument(docId, caseId);
};
const handleNavEdit = (documentId, caseId) => {
if (!documentId || !caseId) {
return;
}
navigate(`/reqedit/${documentId}/interrogatories-out/${caseId}`);
};
const handleUploadDoc = () => {
if (docsAllowed - docsGenerated > 0) {
setUploadType("discovery");
setShowUploadModal(true);
} else {
setShowConfirmModal(true);
}
};
const handleUploadComplaint = () => {
if (docsAllowed - docsGenerated > 0) {
setUploadType("complaint");
setShowUploadModal(true);
} else {
setShowConfirmModal(true);
}
};
const handleConfirmDocType = (docType) => {
setShowConfirmModal(true);
};
const handleCancelConfirm = () => {
setShowConfirmModal(false);
};
const handleConfirmUpgrade = () => {
const isUpgrade = true;
const currentPlan = group?.subscriptionPlan;
navigate(`/signup/${isUpgrade}/${currentPlan}`);
};
const ButtonContent = () => {
return (
<div className="details-button-container">
<div className="details-butn-wrapper">
<div className="details-butn-sub-wrapper-r">
<Link className="view-all-link" to="/cases">
<span className="arrow-wrapper">
<ArrowLeft size="20px" />
</span>
<span className="details-back-wrapper">Back</span>
</Link>
</div>
<div className="details-butn-sub-wrapper-l">
<Button
onClick={() => setShowCreateModal(true)}
labelText="Edit Case Details"
className="primary-button"
/>
</div>
</div>
<div className="details-complaint-button-box">
<Button
className="secondary-button back-button"
onClick={handleUploadComplaint}
labelText="Upload Complaint"
/>
<div className="details-upload-button-box">
<Button
className="p-2 mr-2 primary-button"
onClick={handleUploadDoc}
labelText="Upload Discovery Request"
/>
</div>
</div>
</div>
);
};
if (!group) {
return null;
}
const HeadingContent = () => {
return (
<div className="details-heading-container">
<h3 className="details-heading">Case Information:</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;
const showConfirm = showConfirmModal && 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}
handleConfirmDocType={handleConfirmDocType}
fbUserId={fbUserId}
clientPosition={subCase.clientPosition}
uploadType={uploadType}
/>
) : null
) : null}
{!isLoading ? (
showCreate ? (
<CreateModal setShowModal={setShowCreateModal} caseData={subCase} />
) : null
) : null}
{!isLoading ? (
showConfirm ? (
<ConfirmModal
setShowModal={setShowConfirmModal}
onCancel={handleCancelConfirm}
onConfirm={handleConfirmUpgrade}
modalText="You have used the maximum documents allowed in your current subscription. Upgrade to get more."
titleText="No documents remaining"
buttonLabelText="Upgrade"
/>
) : null
) : null}
{!isLoading ? (
subCase !== null ? (
<DetailCard data={subCase} />
) : null
) : null}
{!isLoading ? <ButtonContent /> : null}
</div>
);
};
export default CaseDetailsPage;
//

View File

@@ -0,0 +1,185 @@
import React, { useEffect, useContext, useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { CaseCard } from "../../pageElements/Cards.js";
import CreateModal from "../Modals/CreateModal.js";
import MobileContent from "../MobileContent.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 size = window.innerWidth < 440;
const [isMobile, setIsMobile] = useState(size);
const count = useRef(null);
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;
const state = group ? group?.state[0].code : null;
useEffect(() => {
if (count.current == null) {
setIsMobile(window.innerWidth < 440);
}
if (count.current < 3) {
setIsMobile(window.innerWidth < 440);
}
return () => {
const temp = (count.current = count.current + 1);
count.current = temp;
};
}, []);
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;
}
const DesktopContent = () => {
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={
state === "ny"
? `Index no. ${c.caseNumber}`
: `Case 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> Please create your first case.</p>
<p>
To create a discovery request or response, you will first enter
some basic information about the case they will pertain to -
i.e. the caption and case number.
</p>
<p>
Then, you will upload a document (i.e. interrogatories or a
complaint), for use in generating a request/response.
</p>
<p>Click "Create New Case" to get started.</p>
{createNewButton()}
</div>
)
) : null}
</>
);
};
return isMobile ? <MobileContent /> : <DesktopContent />;
};
export default CaseListPage;

View File

@@ -0,0 +1,279 @@
import React, { useEffect, useContext, useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { useParams } from "react-router-dom";
import { Typeahead } from "react-bootstrap-typeahead";
import { AppContext } from "../../Hooks/useContext/appContext.js";
import { updateDoc, doc, onSnapshot } from "firebase/firestore";
import Button from "../../pageElements/Button";
import Radiogroup from "../../pageElements/Radiogroup";
import { caseTags } from "../../Constants/Tags/CaseTags";
import { db } from "../../firebase";
import "../../styles/casetype-page.scss";
const CaseTypePage = () => {
const { caseId } = useParams();
const navigate = useNavigate();
const { appState } = useContext(AppContext);
const { group } = appState;
const appUserId = group ? group.appUserId : null;
const [subCase, setSubCase] = useState();
const [contactEmail, setContactEmail] = useState("");
const [contactName, setContactName] = useState("");
const [billingCode, setBillingCode] = useState("");
const [leadAttorney, setLeadAttorney] = useState("");
const [radioValue, setRadioValue] = useState("");
const [selectedCaseTags, setSelectedCaseTags] = useState([]);
const [isBusy, setIsBusy] = useState(false);
const [contactEmailError, setContactEmailError] = useState("");
const [leadAttorneyError, setLeadAttorneyError] = useState("");
const [caseTypeError, setCaseTypeError] = useState(false);
const [clientPositionError, setClientPositionError] = useState();
const typeaheadRef = useRef();
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;
}
const parsedTags = JSON.parse(JSON.stringify(caseTags));
useEffect(getCase, [caseId, appUserId]);
const setSelectedTag = (item) => {
if (selectedCaseTags.length < 5) {
const temp = [...selectedCaseTags, item.tagName];
setSelectedCaseTags(temp);
}
};
const handleTagClick = (arg) => {
const temp = selectedCaseTags;
const foo = temp.filter((tag) => {
if (tag != arg) {
return tag;
}
});
setSelectedCaseTags(foo);
};
const radioOptions = [
{ name: "Plaintiff", label: "Plaintiff", value: "Plaintiff" },
{ name: "Defendant", label: "Defendant", value: "Defendant" },
];
const handleSetRadioValue = (e) => {
setRadioValue(e.value);
};
const validateEmail = (email) => {
const result = /.+@.+\.[A-Za-z]+$/.test(email);
return result;
};
const validateInput = () => {
const leadAttorneyValid = leadAttorney.length > 2;
const contactEmailValid = validateEmail(contactEmail);
const caseTypeValid = selectedCaseTags.length > 0;
const clientPositionValid =
radioValue === "Defendant" || radioValue === "Plaintiff";
const allValid =
leadAttorneyValid &&
contactEmailValid &&
caseTypeValid &&
clientPositionValid;
if (!leadAttorneyValid) {
setLeadAttorneyError("Field cannot be left blank");
}
if (!contactEmailValid) {
setContactEmailError("Field cannot be left blank");
}
if (!caseTypeValid) {
setCaseTypeError("Field cannot be left blank");
}
if (!clientPositionValid) {
setClientPositionError("You must make a selection");
}
return allValid;
};
async function saveCaseData() {
if (isBusy) {
return;
}
const valid = validateInput();
if (valid) {
const recordTags = [];
selectedCaseTags.forEach((tag) => {
parsedTags.forEach((item) => {
if (item.tagName.toLowerCase() === tag.toLowerCase()) {
recordTags.push(item);
}
});
});
const dataValues = {
leadAttorney: leadAttorney,
contactEmail: contactEmail,
contactName: contactName,
clientPosition: radioValue,
caseTags: recordTags,
};
setIsBusy(true);
try {
await updateDoc(doc(db, "cases", caseId), dataValues);
setIsBusy(false);
navigate(`/casedetails/${caseId}`);
} catch (err) {
console.log("Error updating case:", err);
setIsBusy(false);
navigate(`/dashboard`);
}
}
}
return (
<div className="type-container">
<div
clasName="casetype-header"
style={{ marginTop: "22px", marginBottom: "12px" }}
>
<h3 className="doc-header-text">Additional Case Information</h3>
</div>
<div className="type-row">
<div className="type-col">
<input
type="email"
className="form-control"
id="attorneyInput"
placeholder="Lead Attorney"
value={leadAttorney}
onChange={(e) => setLeadAttorney(e.target.value)}
></input>
<div>
{leadAttorneyError ? (
<div className="casetype-error">{leadAttorneyError}</div>
) : (
<></>
)}
</div>
</div>
<div className="type-col">
<input
type="email"
className="form-control"
id="billingInput"
placeholder="Billing Code"
value={billingCode}
onChange={(e) => setBillingCode(e.target.value)}
></input>
</div>
</div>
<div className="type-row">
<div className="type-col">
<input
type="email"
className="form-control"
id="emailInput"
placeholder="Contact Name"
value={contactName}
onChange={(e) => setContactName(e.target.value)}
></input>
</div>
<div className="type-col">
<input
type="email"
className="form-control"
id="emailInput"
placeholder="Contact Email"
value={contactEmail}
onChange={(e) => setContactEmail(e.target.value)}
></input>
{contactEmailError ? (
<div className="casetype-error">{contactEmailError}</div>
) : (
<></>
)}
</div>
</div>
<div className="type-row">
<div className="type-col create-case-taginput">
<Typeahead
className="flex-grow-1"
id="case-type-search"
labelKey="tagName"
options={parsedTags}
minLength={1}
ref={typeaheadRef}
placeholder="Case Type - type to search, tab/click to select (max 5)"
onChange={(selected) => {
let item = null;
for (const currentItem of selected) {
item = { ...currentItem };
}
setSelectedTag(item);
typeaheadRef.current.clear();
}}
/>
{caseTypeError ? (
<div className="casetype-error">{caseTypeError}</div>
) : (
<></>
)}
<div className="case-tag-container">
{selectedCaseTags.map((tag, i) => (
<div className="tag-container">
<div className="tag-box">
<div
className="tag-ex-box"
onClick={() => handleTagClick(tag)}
>
<div className="tag-ex">x</div>
</div>
<div className="casetag-name">{tag}</div>
</div>
</div>
))}
</div>
</div>
<div className="type-col">
<div className="radio-wrapper">
<Radiogroup
title="Client position"
options={radioOptions}
value={radioValue}
onClick={(e) => handleSetRadioValue(e, "clientPosition")}
/>
</div>
{clientPositionError ? (
<div className="casetype-error">{clientPositionError}</div>
) : (
<></>
)}
</div>
</div>
<div className="casetype-button-container">
<Button
className="pt-2 pb-2 mr-2 primary-button"
onClick={saveCaseData}
labelText="Next"
/>
</div>
</div>
);
};
export default CaseTypePage;

View File

@@ -0,0 +1,336 @@
import React, { useState, useContext, useEffect, useRef } from "react";
import { CaseCard } from "../../pageElements/Cards.js";
import { Link, useNavigate, createSearchParams } from "react-router-dom";
import MobileContent from "../MobileContent";
import {
ArrowRight,
Activity,
FileEarmarkText,
Pencil,
Clock,
} from "react-bootstrap-icons";
import { db } from "../../firebase.js";
import {
collection,
doc,
setDoc,
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";
import { getAuth } from "firebase/auth";
const Dashboard = () => {
const size = window.innerWidth < 440;
const [isMobile, setIsMobile] = useState(size);
const auth = getAuth();
const count = useRef(null);
const navigate = useNavigate();
const [allCases, setAllCases] = useState(null);
const [selectedCase, setSelectedCase] = useState(null);
const [showModal, setShowModal] = useState();
const [docCount, setDocCount] = useState();
const [isPromoFirstLogin, setIsPromoFirstLogin] = useState(true);
const [responseCount, setResponseCount] = useState();
const { appState } = useContext(AppContext);
const { group } = appState;
const appUserId = group ? group.appUserId : null;
const _fbAuthUid = group ? group.fbAuthUid : null;
const isDashboard = true;
group && console.log("group.tpe", group?.tpe);
console.log("auth", auth);
console.log("appUserId", appUserId);
useEffect(() => {
if (count.current == null) {
setIsMobile(window.innerWidth < 440);
}
if (count.current < 3) {
setIsMobile(window.innerWidth < 440);
}
return () => {
const temp = (count.current = count.current + 1);
count.current = temp;
};
}, []);
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;
}
async function handleClick(e) {
e.preventDefault();
const updateRef = collection(db, "users");
await setDoc(doc(updateRef, _fbAuthUid), {
isPromotionalFirstLogin: false,
});
navigate({
pathname: "/passwordreset/",
search: `?${createSearchParams({
mode: "enterEmail",
})}`,
});
}
const DesktopContent = () => {
return 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;
};
if (!group) {
return;
}
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: "#ff6200" }} />
</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>
{responseCount > 0 ? (
<>
<div className="stats-unit-wrapper">
<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>
</>
) : (
<></>
)}
{responseCount > 0 ? (
<>
<div className="stats-unit-wrapper">
<div className="stats-icon-wrapper">
<Clock style={{ color: "#ff6200" }} />
</div>
<div>
<p className="stats-text">
{Math.round(responseCount * 3.1799 * 10) / 10} hours saved
using Novodraft
</p>
</div>
</div>
</>
) : (
<></>
)}
</div>
</div>
{isMobile ? (
<MobileContent isDashboard={isDashboard} />
) : (
<DesktopContent />
)}
{showModal && selectedCase !== null ? (
<UploadModal setShowModal={setShowModal} caseData={selectedCase} />
) : null}
</>
);
};
export default Dashboard;
/*
{isPromoFirstLogin ? (
<>
<div className="dash-promo-first-box">
<div className="dash-promo-first-wrap">
<div className="dash-promo-first-textwrap">
<div className="dash-promo-first-text">
<p className="dash-welcome-text">
Welcome to Novodraft, attorney {group.lastName}. We are
excited to introduce you to our AI application. Review the
step-by-step user guide by clicking "How-to" in the above
navigation bar.
</p>
<p className="dash-welcome-text">
Your login is username: <strong>{group.email}</strong> with
a temporary password: <strong>{group.tpe}</strong>. You
should change your password now by{" "}
<span onClick={handleClick} className="dash-clicknow-span">
clicking here.
</span>
</p>
</div>
</div>
</div>
</div>
<div className="app-divider dash-divider" />
</>
) : null}
*/

View File

@@ -0,0 +1,661 @@
import { useState, useContext, useEffect } from "react";
import NewYorkCaption from "./captionHeaders/newYorkCaption.js";
import NewJerseyCaption from "./captionHeaders/newJerseyCaption.js";
import FloridaCaption from "./captionHeaders/floridaCaption.js";
import MichiganCaption from "./captionHeaders/michiganCaption.js";
import { useParams } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import EditElement from "../../pageElements/EditElement";
import { db } from "../../firebase";
import { AppContext } from "../../Hooks/useContext/appContext";
import Button from "../../pageElements/Button";
import ConfirmModal from "../Modals/ConfirmModal";
import LoadingSpinner from "../../pageElements/LoadingSpinner";
import { docEditCopy } from "../../Constants/Copy/docEditCopy.js";
import { doc, getDoc, updateDoc, increment } from "firebase/firestore";
import "../../styles/docedit-page.scss";
const DocEditPage = (props) => {
const navigate = useNavigate();
const { documentId, caseId, docType, clientPosition, responseGenerations } =
useParams();
const [document, setDocument] = useState(null);
const [_docType, setDocType] = useState(docType);
const [fetchedCase, setFetchedCase] = useState(null);
const [parsedRogs, setParsedRogs] = useState([]);
const [responses, setResponses] = useState([]);
const [showInputEle, setShowInputEle] = useState(true);
const { appState } = useContext(AppContext);
const { group } = appState;
const appUserId = group ? group.appUserId : null;
const [isBusy, setIsBusy] = useState(false);
const fbUserId = group ? group.fbAuthUid : null;
const [showSaveModal, setShowSaveModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isReport, setIsReport] = useState(false);
const [issueText, setIssueText] = useState("");
const [showErrorModal, setShowErrorModal] = useState(false);
const [responsesCreated, setResponsesCreated] = useState(
parseInt(responseGenerations)
);
// For document generation:
const state = group?.state[0] ? group.state[0].code : "ny";
const lawFirm = group ? group?.firm : null;
const streetAdd = group ? group?.streetAddress : null;
const city = group ? group?.city : null;
const usState = group?.state[0] ? group?.state[0].code : "ny";
const zipCode = group ? group?.zipCode : null;
const telephone = group ? group?.telephone : null;
const respondent =
fetchedCase?.clientPosition == "Defendant"
? fetchedCase?.defendantParties
: fetchedCase?.plaintiffParties;
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
const displayCopy =
state === "ny"
? docEditCopy.NewYork
: state === "mi"
? docEditCopy.Michigan
: state === "nj"
? docEditCopy.NewJersey
: docEditCopy.NewJersey;
const headerPicker = () => {
switch (state) {
case "ny":
return (
<NewYorkCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
case "nj":
return (
<NewJerseyCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
case "fl":
return (
<FloridaCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
case "mi":
return (
<MichiganCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
default:
return (
<NewYorkCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
}
};
const headerString =
docType === "interrogatories" ? (
"Response to Interrogatories"
) : docType === "admissions" ? (
"Response to Request for Admissions"
) : docType === "production" ? (
"Response to Request for Production"
) : docType === "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.",
];
async function getDocument(docId) {
try {
const docRef = doc(db, "documents", `${docId}`);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const data = docSnap?.data();
return data;
} else {
console.log("DB item does not exist");
}
} catch (error) {
console.log(`A system error has occurred: ${error}`);
}
}
async function getCase(caseId) {
try {
const docRef = doc(db, "cases", `${caseId}`);
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}`);
}
}
async function getParsedRequests(documentId, docType) {
if (!documentId || !docType) {
return;
}
try {
const response = await fetch(
`${apiUrl}/v1/get-parsed-requests/${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);
}
}
useEffect(() => {
if (!documentId) {
return;
}
getDocument(documentId)
.then((data) => {
setDocument(data);
const tempType = data?.docType;
return tempType;
})
.then((type) => {
setDocType(type);
});
}, [documentId]);
useEffect(() => {
if (!caseId) {
return;
}
getCase(caseId);
}, [caseId, documentId]);
useEffect(() => {
getParsedRequests(documentId, docType);
}, [documentId, docType]);
async function getCompletions(docId, docType) {
if (!docId || !docType) {
return;
}
const response = await fetch(
`${apiUrl}/v1/get-completions/${docId}/${docType}`,
{
method: "GET",
}
);
const resp = await response.json();
return resp;
}
async function generateRespIncominIrregDiscReq(docId, clientPosition) {
const isRequests = true;
try {
fetch(
`${apiUrl}/v1/generate-disc-responses-irreg/${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(docId);
setIsLoading(false);
})
.catch((err) => console.log(err));
} catch (err) {
console.log("gen/esp inresp err", err);
}
}
async function updateRespGenerationCount(docId) {
await updateDoc(doc(db, "documents", docId), {
responseGenerations: increment(1),
});
setResponsesCreated((responsesCreated) => responsesCreated + 1);
}
useEffect(() => {
if (!docType) {
return;
}
if (docType != "combined-numbered") {
return;
}
if (docType == "combined-numbered") {
setIsLoading(true);
generateRespIncominIrregDiscReq(documentId, clientPosition);
}
}, [documentId, docType, docType]);
useEffect(() => {
if (!docType) {
return;
}
if (docType != "combined-numbered") {
setIsLoading(true);
getCompletions(documentId, docType)
.then((data) => {
const resp = data[0].responses.map((item, index) => {
// NEVER CHANGE THIS:
return { showInputEle: false, resp: item?.text, index: index };
});
setResponses(resp);
setIsLoading(false);
})
.catch((err) => console.log(err));
}
}, [documentId, docType]);
async function postEditedResponses(docId, docType) {
let obj = {};
obj["type"] = docType;
obj["id"] = docId;
obj["requests"] = responses ? responses : null;
const data = JSON.stringify(obj);
try {
const response = await fetch(`${apiUrl}/v1/store-edited-completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
json: true,
body: data,
});
const res = response;
return res;
} catch (error) {
console.error("Error sending resp to server:", error);
}
}
async function cleanUpDocx() {
const docId = documentId;
fetch(`${apiUrl}/cleanUpDocx/${docId}`, {
method: "GET",
})
.then((response) => {
//
})
.catch((err) => {
console.log(err);
});
}
async function createAndReturnDocx() {
setIsBusy(true);
const response = await handleCreateDocx();
if (response?.status != 500) {
setTimeout(getDocx, 5000);
setTimeout(cleanUpDocx, 10000);
} else {
setShowErrorModal(true);
}
window.scrollTo(0, 0);
setTimeout(setIsBusy, 4000, false);
//setIsBusy(false);
}
async function getDocx() {
const docId = documentId;
const reqType = docType;
fetch(`${apiUrl}/v1/get-docx/${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 postDocxData(obj) {
const docId = documentId;
const response = await fetch(`${apiUrl}/v1/store-docx-data/${docId}`, {
method: "POST",
body: JSON.stringify(obj),
});
//return res;
}
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 = docType;
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;
const currentRequestType = docType;
try {
const response = await fetch(`${apiUrl}/v1/store-docx-data/${docId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
caseCaption1: captionOne,
caseCaption2: captionTwo,
caseNumber: caseNum,
clientPosition: clientPosition,
currentRequestType: currentRequestType,
defendant: defendants,
firm: lawFirm,
firmCity: city,
firmState: usState.code,
firmStreetAddress: streetAdd,
firmZip: zipCode,
judge: judge,
jurisdiction: juris,
leadAttorneys: leadAttorneys,
plaintiff: plaintiffs,
state: state,
tel: telephone,
venue: venued,
}),
});
if (response.status === 200) {
const resp = await fetch(
`${apiUrl}/v1/generate-request-docx/${docId}`,
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
);
return resp;
}
} catch (err) {
console.log("err in postDocxData", err);
}
}
const handleConfirmReport = () => {
setShowSaveModal(true);
setIsReport(true);
};
const handleConfirmSave = () => {
setShowSaveModal(true);
};
const handleSave = () => {
postEditedResponses();
setShowSaveModal(false);
};
const handleCancelSave = () => {
setShowSaveModal(false);
};
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 (docType === "interrogatories") {
numText = "INTERROGATORY NO..";
} else if (docType === "admissions") {
numText = "REQUEST FOR ADMISSION NO.";
} else if (docType === "production") {
numText = "REQUEST FOR PRODUCTION NO.";
} else {
numText = "REQUEST NO.";
}
return <div className="numbering-container">{`${numText}${i + 1} `}</div>;
};
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 handleReport = () => {
setShowSaveModal(false);
setIsReport(false);
};
const handleBack = () => {
navigate(`/documents`);
};
const editingContent = () => {
return (
<>
<div> {headerPicker()}</div>
<div className="doc-editing-wrapper">
<div className="pleading-header">{headerString}</div>
<div>
{displayCopy.prelimaryStatement(docType, respondent, servingParty)}
</div>
<div className="doc-editing-sub-wrapper">
{parsedRogs?.map((rog, i) => (
<div className="req-item-outer-div">
<div className="doced-request-text-up">
{numberingContent(i)}
{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="docedit-report-container"
onClick={() => handleConfirmReport()}
>
<div className="docedit-report-link">Report an issue</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 && !isReport && documentId !== null ? (
<ConfirmModal
onCancel={handleCancelSave}
onConfirm={handleSave}
buttonLabelText="Confirm Save"
modalText={
"Are you sure? This will overrwrite any previously-saved edits."
}
/>
) : null}
{showSaveModal && isReport && documentId !== null ? (
<ConfirmModal
isReport={isReport}
onCancel={handleCancelSave}
onConfirm={handleReport}
buttonLabelText="Report Issue"
documentId={documentId}
caseId={caseId}
appUserId={appUserId}
issuetext={issueText}
setIssueText={setIssueText}
modalText={
"Date/time and document id automatically logged. Support will address this within 24 hours."
}
/>
) : null}
{showErrorModal ? (
<ConfirmModal
onCancel={() => setShowErrorModal(false)}
onConfirm={() => setShowErrorModal(false)}
buttonLabelText="Confirm"
modalText={
"There was a problem. Support was notified and will respond within 24 hours."
}
/>
) : null}
</>
);
};
const readyToEdit = fetchedCase && responses.length > 1;
return isLoading ? (
<LoadingSpinner message={messages[0]} />
) : isBusy ? (
<LoadingSpinner message={"Downloading .docx"} loaderType="MoonLoader" />
) : 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,314 @@
import { useContext, useEffect, useState, useRef } 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 Toggle from "../../pageElements/Toggle.js";
import MobileContent from "../MobileContent";
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 size = window.innerWidth < 440;
const [isMobile, setIsMobile] = useState(size);
const count = useRef(null);
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 [isBusy, setIsBusy] = useState(false);
const [selectedDocumentType, setSelectedDocumentType] = useState();
const [showModal, setShowModal] = useState(false);
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
useEffect(() => {
if (count.current == null) {
setIsMobile(window.innerWidth < 440);
}
if (count.current < 3) {
setIsMobile(window.innerWidth < 440);
}
return () => {
const temp = (count.current = count.current + 1);
count.current = temp;
};
}, []);
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);
}
const handleNovos = (e) => {
navigate("/how-to");
};
async function deleteFromStorage() {
const selectedDoc = allDocs.filter((doc) => {
return doc.documentId == selectedDocumentId;
});
const respGens = selectedDoc[0].responseGenerations;
try {
const response = await fetch(
`${apiUrl}/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" },
];
function handleNavigate(
documentId,
caseId,
documentType,
clientPosition,
responseGenerations
) {
const docId = String(documentId);
const docType = String(documentType);
if (documentType === "interrogatories-out") {
//may include other in future ie req for admission
navigate(`/reqedit/${docId}/interrogatories-out/${caseId}`);
} else {
navigate(
`/docedit/${docId}/${caseId}/${docType}/${clientPosition}/${responseGenerations}`
);
}
}
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;
}
const DesktopContent = () => {
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="content-container">
{allDocs.length > 0 ? (
<div className="document-list-upper-wrapper">
<div className="list-upper-left"></div>
<div className="list-upper-right">
<div className="d-flex justify-content-end mb-3">
<SelectDropdown
dropDownOptions={dropDownOptions}
titleText="Sort documents"
handleSelect={setOrder}
/>
</div>
</div>
</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}
createdAt={doc.createdAt}
docType={doc.docType}
documentId={doc.documentId}
dateServed={doc.dateServed}
confirmDelete={confirmDelete}
handleNavigate={handleNavigate}
displayDeleteButton={true}
responseGenerations={doc.responseGenerations}
clientPosition={doc.clientPosition}
/>
))
) : (
<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}
</>
);
};
return isMobile ? <MobileContent /> : <DesktopContent />;
};
export default DocumentListPage;
/*
const [verbValue, setVerbValue] = useState(1);
const handleToggle = (val) => setVerbValue(val);
<div className="toggle-outer">
<div className="toggle-header">
{" "}
<p
onClick={handleNovos}
href=""
className="doclist-toggle-link"
>
Novos
</p>
</div>
<div className="toggle-box">
<p className="select-billing-text">Min</p>
<Toggle value={verbValue} onClick={handleToggle} />
<p className="select-billing-text">Max</p>
</div>
</div>
*/

View File

@@ -0,0 +1,50 @@
import NewYorkCaption from "./captionHeaders/newYorkCaption.js";
import NewJerseyCaption from "./captionHeaders/newJerseyCaption.js";
import FloridaCaption from "./captionHeaders/floridaCaption.js";
import { docEditCopy } from "../../Constants/Copy/docEditCopy.js";
const headerPicker = (state, fetchedCase, onScrollClick) => {
const displayCopy =
state === "ny"
? docEditCopy.NewYork
: state === "nj"
? docEditCopy.NewJersey
: docEditCopy.NewYork;
switch (state) {
case "ny":
return (
<NewYorkCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
case "nj":
return (
<NewJerseyCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
case "fl":
return (
<FloridaCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
default:
return (
<NewYorkCaption
displayCopy={displayCopy}
fetchedCase={fetchedCase}
onScrollClick={onScrollClick}
/>
);
}
};
export default headerPicker;

View File

@@ -0,0 +1,606 @@
import { useState, useContext, useEffect } from "react";
import headerPicker from "./HeaderPicker";
import { useParams } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import { AppContext } from "../../Hooks/useContext/appContext";
import EditElement from "../../pageElements/EditElement";
import LoadingSpinner from "../../pageElements/LoadingSpinner";
import {
docEditCopy,
outgoingComesNow,
} from "../../Constants/Copy/docEditCopy.js";
import Button from "../../pageElements/Button";
import {
rogInstructions,
rogDefinitions,
foundationRogs,
standardProd,
} from "../../Constants/Copy/reqEditCopy.js";
import { db } from "../../firebase";
import { doc, getDoc } from "firebase/firestore";
import "../../styles/doclist-page.scss";
const RequestEditPage = () => {
const { documentId, documentType, caseId } = useParams();
const { appState } = useContext(AppContext);
const { group } = appState;
const navigate = useNavigate();
const [requests, setRequests] = useState([1]);
const [prodReq, setProdReq] = useState();
const [fetchedCase, setFetchedCase] = useState();
const [showInputEle, setShowInputEle] = useState(true);
const [showprodInputEle, setShowprodInputEle] = useState(true);
const [document, setDocument] = useState(null);
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 [isReport, setIsReport] = useState(false);
const [showErrorModal, setShowErrorModal] = useState(false);
const [isBusy, setIsBusy] = useState(false);
// Case Vars
const state = group?.state[0] ? group.state[0].code : "ny";
const lawFirm = group ? group?.firm : null;
const streetAdd = group ? group?.streetAddress : null;
const city = group ? group?.city : null;
const usState = group?.state[0] ? group?.state[0].code : "ny";
const zipCode = group ? group?.zipCode : null;
const telephone = group ? group?.telephone : null;
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
useEffect(() => {
if (!appUserId || !documentId) {
setDocument(null);
return;
}
try {
getCase(caseId);
//getDocument();
} catch (err) {
console.log("Error:", err);
}
}, [appUserId, documentId, caseId]);
useEffect(() => {
if (!documentId) {
return;
}
getOutgoingRequests(documentId)
.then((data) => {
const merged = [...foundationRogs, ...data[0].requests];
const resp = merged?.map((item, index) => {
// NEVER CHANGE THIS:
return { showInputEle: false, text: item.text, index: index };
});
if (resp.length > 10) {
setRequests(resp);
setProdReq(standardProd);
}
})
.catch((err) => console.log(err));
}, [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}`);
}
}
async function getOutgoingRequests(documentId) {
if (!documentId) {
return;
}
const docType = String(documentType);
const docId = String(documentId);
const response = await fetch(
`${apiUrl}/v1/get-outgoing-requests/${docId}/${docType}`,
{
method: "GET",
}
);
const req = await response.json();
return req;
}
async function getDocx() {
const docId = documentId;
const reqType = documentType;
fetch(`${apiUrl}/v1/get-docx/${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 = `interrogatories-req-prod.docx`;
a.click();
});
});
}
async function cleanUpDocx() {
const docId = documentId;
const reqType = documentType;
fetch(`${apiUrl}/cleanUpDocx/${docId}/${reqType}`, {
method: "GET",
})
.then((response) => {
setIsBusy(false);
})
.catch((err) => {
console.log(err);
});
}
async function createAndReturnDocx() {
setIsBusy(true);
const response = await handleCreateDocx();
if (response?.status != 500) {
setTimeout(getDocx, 5000);
setTimeout(cleanUpDocx, 10000);
} else {
setShowErrorModal(true);
}
window.scrollTo(0, 0);
setTimeout(setIsBusy, 4000, false);
//setIsBusy(false);
}
const postEditedrequests = () => {};
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" });
}
};
async function postEditedResponses() {
if (!requests && requests.length > 1) {
return;
}
const mergedRequests = [...requests, ...prodReq];
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;
const caseInfo = {
caseCaption1: captionOne,
caseCaption2: captionTwo,
caseNumber: caseNum,
clientPosition: clientPosition,
currentRequestType: reqType,
defendant: defendants,
firm: lawFirm,
firmCity: city,
firmState: usState.code,
firmStreetAddress: streetAdd,
firmZip: zipCode,
judge: judge,
jurisdiction: juris,
leadAttorneys: leadAttorneys,
plaintiff: plaintiffs,
state: state,
tel: telephone,
venue: venued,
};
let obj = {};
obj["type"] = "interrogatories-out";
obj["id"] = docId;
obj["requests"] = mergedRequests;
obj["caseInfo"] = caseInfo;
const data = JSON.stringify(obj);
try {
const response = await fetch(`${apiUrl}/v1/store-edited-completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
json: true,
body: data,
});
return response;
} catch (error) {
console.error("Error sending responses to the server:", error);
}
}
async function handleCreateDocx(docType) {
// TODO: compare to see if any changes made before gen, save those
const docId = documentId;
const leadAttorneys = fetchedCase?.leadAttorneys
? fetchedCase?.leadAttorneys
: null;
const reqType = docType;
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;
const currentRequestType = "interrogatories-out";
try {
const response = await fetch(`${apiUrl}/v1/store-docx-data/${docId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
caseCaption1: captionOne,
caseCaption2: captionTwo,
caseNumber: caseNum,
clientPosition: clientPosition,
currentRequestType: currentRequestType,
defendant: defendants,
firm: lawFirm,
firmCity: city,
firmState: usState.code,
firmStreetAddress: streetAdd,
firmZip: zipCode,
judge: judge,
jurisdiction: juris,
leadAttorneys: leadAttorneys,
plaintiff: plaintiffs,
state: state,
tel: telephone,
venue: venued,
}),
});
if (response.status === 200) {
const resp = await fetch(
`${apiUrl}/v1/generate-request-docx/${docId}`,
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
);
return resp;
}
} catch (err) {
console.log("err in postDocxData", err);
}
}
const handleBlur = (e, i) => {
const newSelectedrequests = requests?.map((r) => {
if (r.index === i) {
return {
...r,
showInputEle: !showInputEle,
};
}
return r;
});
setRequests(newSelectedrequests);
};
const handleProdBlur = (e, i) => {
const newSelectedrequests = requests?.map((r) => {
if (r.index === i) {
return {
...r,
showInputEle: !showInputEle,
};
}
return r;
});
setRequests(newSelectedrequests);
};
const handleFocus = (arg, i) => {
const newSelectedrequests = requests?.map((r) => {
if (r.index === i) {
return {
...r,
showInputEle: !showInputEle,
};
}
return r;
});
setRequests(newSelectedrequests);
};
const handleProdFocus = (arg, i) => {
const newSelectedrequests = requests?.map((r) => {
if (r.index === i) {
return {
...r,
showInputEle: !showInputEle,
};
}
return r;
});
setRequests(newSelectedrequests);
};
const handleEditValue = (e, i) => {
const editingRes = requests?.map((r) => {
if (r.index === i) {
return {
...r,
text: e.target.value,
};
}
return r;
});
setRequests(editingRes);
};
const handleEditProdValue = (e, i) => {
const editingProd = standardProd?.map((r) => {
if (r.index === i) {
return {
...r,
text: e.target.value,
};
}
return r;
});
setProdReq(editingProd);
};
const handleBack = () => {
navigate(`/documents`);
};
const handleCancelSave = () => {
setShowSaveModal(false);
};
const handleSave = () => {
postEditedrequests();
setShowSaveModal(false);
};
const handleReport = () => {
setShowSaveModal(false);
setIsReport(false);
};
const handleConfirmSave = () => {
setShowSaveModal(true);
};
const handleConfirmReport = () => {
setShowSaveModal(true);
setIsReport(true);
};
const respondent =
fetchedCase?.clientPosition == "Defendant"
? fetchedCase?.defendantParties
: fetchedCase?.plaintiffParties;
const displayCopy =
state === "ny"
? docEditCopy?.NewYork
: state === "nj"
? docEditCopy?.NewJersey
: docEditCopy?.NewYork;
const servingParty =
fetchedCase?.clientPosition === "Defendant"
? fetchedCase?.plaintiffParties
: fetchedCase?.defendantParties;
const editingContent = () => {
return (
<>
<div> {headerPicker(state, fetchedCase, onScrollClick)}</div>
<div className="doc-editing-wrapper">
<div className="pleading-header">
INTERROGATORIES AND REQUESTS FOR PRODUCTION
</div>
<div className="req-edit-comesnow-box">
{outgoingComesNow(state, fetchedCase)}
</div>
{/*displayCopy.prelimaryStatement*/}
<div className="request-editing-sub-wrapper row">
<div className="pleading-header">INSTRUCTIONS</div>
{rogInstructions?.map((req, i) => (
<div className="request-text-up-row">
<div className="request-text-number-col">{`${i + 1}.`}</div>
<div>{req?.text}</div>
</div>
))}
</div>
<div className="request-editing-sub-wrapper row">
<div className="pleading-header">DEFINITIONS</div>
{rogDefinitions?.map((req, i) => (
<div className="request-text-up-row">
<div className="request-text-number-col">{`${i + 1}.`}</div>
<div>{req?.text}</div>
</div>
))}
</div>
<div className="request-editing-sub-wrapper row">
<div className="pleading-header">INTERROGATORIES</div>
{requests?.map((req, i) => (
<div className="request-text-up-row">
<div className="request-text-number-col-low">{`${i + 1}.`}</div>
<div className="req-edit-wrapper">
<div className="req-item-inner-div">
<EditElement
value={
requests && requests.length > 2
? // NEVER CHANGE THIS
req.text
: null
}
setShowInputEle={setShowInputEle}
showInputEle={
requests && requests.length > 2
? req.showInputEle
: null
}
handleFocus={handleFocus}
handleEditValue={handleEditValue}
handleBlur={handleBlur}
i={i}
/>
</div>
</div>
</div>
))}
</div>
<div className="request-editing-sub-wrapper row">
<div className="pleading-header">REQUESTS FOR PRODUCTION</div>
{prodReq?.map((preq, i) => (
<div className="request-text-up-row">
<div className="request-text-number-col">{`${i + 1}.`}</div>
<div className="req-edit-wrapper">
<div className="req-item-inner-div">
<EditElement
value={
standardProd && standardProd.length > 2
? // NEVER CHANGE THIS
preq.text
: null
}
setShowInputEle={setShowInputEle}
showInputEle={
standardProd && standardProd.length > 2
? preq.showInputEle
: null
}
handleFocus={handleFocus}
handleEditValue={handleEditValue}
handleBlur={handleBlur}
i={i}
/>
</div>
</div>
</div>
))}
</div>
<div
className="reqedit-report-container"
onClick={() => handleConfirmReport()}
>
<div className="docedit-report-link">Report an issue</div>
</div>
<div className="req-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>
</>
);
};
//const readyToEdit = fetchedCase && requests.length > 1;
const readyToEdit = true;
const messages = [
"Generating responses. Please be patient, this may take several minutes.",
];
return isLoading ? (
<LoadingSpinner message={messages[0]} />
) : isBusy ? (
<LoadingSpinner message={"Downloading .docx"} loaderType="MoonLoader" />
) : readyToEdit ? (
editingContent()
) : (
<div className="docedit-errstate-container">
<div className="nodocs-text-container">
<p> 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 RequestEditPage;
/*
async function handleCreateDocx() {
const res = await postEditedResponses();
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
await sleep(2000);
try {
const response = await fetch(
`${apiUrl}/v1/generate-request-docx/${docId}`,
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
docId: docId,
}),
}
);
return response;
} catch (error) {
console.error("Error sending requests to the server:", error);
}
}
*/

View File

@@ -0,0 +1,74 @@
import Scrollbutton from "react-bootstrap/Button";
import { ArrowUp, ArrowDown } from "react-bootstrap-icons";
import "../../../styles/captions.scss";
const FloridaCaption = (props) => {
const { fetchedCase, displayCopy, onScrollClick } = props;
return (
<>
<div className="docedit-super-row">
<div className="docedit-uppercol1"></div>
<div className="docedit-uppercol2 florida-court-header">
<div className="florida-court-header-text">
IN THE CIRCUIT COURT OF THE{" "}
{fetchedCase?.jurisdiction.toUpperCase()}
</div>
<div className="florida-court-header-text2">
OF FLORIDA, IN AND FOR {fetchedCase?.venue.toUpperCase()} COUNTY
</div>
<div className="pleading-divider-box"></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 florida-inner">
{fetchedCase?.caption ? fetchedCase?.caption.toUpperCase() : <></>},
</div>
<div className="header-middle-row2">
<div className="content-spacer"></div>
<div className="middle-row-inner florida-inner">
<div>Plaintiff(s),</div>
<div className="casenum-box">
Case No. {fetchedCase?.caseNumber}
</div>
</div>
</div>
<div className="header-middle-row3">
<div>v.</div>
{/*<div>Judge: {fetchedCase?.judge}</div> */}
</div>
<div className="header-middle-row4 florida-inner">
{fetchedCase?.captionTwo ? (
fetchedCase?.captionTwo.toUpperCase()
) : (
<></>
)}
,
</div>
<div className="header-middle-row5">
<div className="content-spacer2"></div>
<div className="florida-inner">Defendant(s)</div>
</div>
<div className="pleading-divider-box">
<div>
________________________________________________________________/
</div>
</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 FloridaCaption;

View File

@@ -0,0 +1,64 @@
import Scrollbutton from "react-bootstrap/Button";
import { ArrowUp, ArrowDown } from "react-bootstrap-icons";
const MichiganCaption = (props) => {
const { fetchedCase, displayCopy, onScrollClick } = props;
return (
<>
<div className="docedit-super-row">
<div className="docedit-uppercol1"></div>
<div className="docedit-uppercol2 centered-text">
<span>STATE OF MICHIGAN</span>
<span>
IN THE CIRCUIT COURT FOR THE COUNTY OF{" "}
{fetchedCase?.venue.split(" ")[0].toUpperCase()}
</span>
</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 plain-text">
<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">
Case No. {fetchedCase?.caseNumber}
</div>
</div>
</div>
<div className="header-middle-row3">
<div>{displayCopy.versusText}</div>
<div>Hon. {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="pleading-divider-box">
<div>__________________________________________________/</div>
</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 MichiganCaption;

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,68 @@
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="pleading-divider-box">
<div>
-----------------------------------------------------------------------X
</div>
</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="pleading-divider-box">
<div>
----------------------------------------------------------------------------X
</div>
</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,44 @@
import {
FeaturesTop,
FeaturesBottom,
} from "../../Constants/Copy/featuresCopy.js";
import "../../styles/features.scss";
const FeaturesPage = () => {
return (
<>
<div className="features-container">
<div className="features-top-wrapper">
<div className="features-top-mobile-img">
<img
className="features-image-one"
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/79eafada-1517-43a6-99f1-d3e2b0da8200/public"
/>
</div>
<div className="features-top-left">
<FeaturesTop />
</div>
<div className="features-top-right">
<img
className="features-image-one"
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/79eafada-1517-43a6-99f1-d3e2b0da8200/public"
/>
</div>
</div>
<div className="features-bottom-wrapper">
<div className="features-bottom-left">
<img
className="features-image-one"
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/832e2844-0133-4822-6710-a808a98aa500/public"
/>
</div>
<div className="features-bottom-right">
<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" id="scroll-target-id">
<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,157 @@
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, supportFields } from "../../Constants/Fields/DemoFields";
import { useParams } from "react-router-dom";
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 { supReq } = useParams();
const navigate = useNavigate();
const token = window.sessionStorage.getItem("token");
const [isBusy, setIsBusy] = useState();
const fieldsChunkSize = 2;
const isSupport = token && supReq === "5ac45d12";
const inputFields = isSupport ? supportFields : demoFields;
const [data, setData] = useState(getFormDataDefaults(inputFields));
const handleChangeInput = (e, name) => {
const newData = handleFormDataChange(e, name, data, inputFields);
if (newData !== null) {
setData(newData);
}
};
const validateData = () => {
const newData = getValidatedFormData(data, inputFields);
const hasErrors = isFormDataHasErrors(newData);
setData(newData);
return hasErrors ? null : objectMap(({ value }) => value, newData);
};
async function saveUserData(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 saveSupportRequestData(dataValues) {
const requestDate = new Date();
dataValues["requestDate"] = requestDate;
const requestId = uuidv4();
try {
const supportRef = collection(db, "supportrequests");
await setDoc(doc(supportRef, requestId), dataValues);
} catch (error) {
console.log(`Error saving request data to db: ${error}`);
}
}
async function handleRequestDemo() {
const dataValues = validateData();
if (dataValues === null) {
return;
}
if (isSupport) {
saveSupportRequestData(dataValues);
navigate("/");
} else {
saveUserData(dataValues);
navigate("/");
}
}
return (
<div className="signup-super-container">
<div className="signup-sub-container">
<div className="signup-header">
{isSupport ? (
<h2 className="signup-header-text">
Request Technical Or Account Support
</h2>
) : (
<>
<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(inputFields), fieldsChunkSize).map(
(names, j) => (
<Row key={`row${j}`} className="demo-page-row">
{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
? inputFields[name].label
: ""
}
type={inputFields[name].type}
values={inputFields[name].values}
disabled={isBusy}
/>
</Col>
))}
</Row>
)
)}
</Form>
<div className="demo-button-box">
<Button
className="primary-button demo"
type="button"
size="lg"
onClick={handleRequestDemo}
disabled={isBusy}
labelText={isSupport ? "Request Support" : "Request Demo"}
/>
<div className="mt-3">
{isSupport ? (
<></>
) : (
<span className="demo-link-span">
<Link to="/signup">Go to signup</Link>
</span>
)}
</div>
</div>
</div>
</div>
);
};
export default DemoRequestPage;

View File

@@ -0,0 +1,538 @@
import { useEffect, useState } from "react";
import { collection, setDoc, doc } from "firebase/firestore";
import { db, auth } from "../../firebase";
import Button from "../../pageElements/Button";
import { v4 as uuidv4 } from "uuid";
import arrow from "../../Assets/Images/Arrow.png";
import { useNavigate } from "react-router-dom";
import ConfirmModal from "../Modals/ConfirmModal";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { stateDataValues } from "../../Constants/Fields/SignupFields";
import "../../styles/homepage.scss";
import LoadingSpinner from "../../pageElements/LoadingSpinner";
const HomePage = () => {
const navigate = useNavigate();
const [showPromoModal, setShowPromoModal] = useState(false);
const [isBusy, setIsBusy] = useState(false);
const [notice, setNotice] = useState("");
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
useEffect(() => {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "screen_view", {
app_name: "novodraft",
screen_name: "Home",
});
}
}, []);
function clickedTryItButton() {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "try_it_click", {
app_name: "novodraft",
screen_name: "Home",
});
}
}
function promoSignup() {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "promo_sign_up", {
send_to: "G-J6SQL5DF6V",
});
}
}
function clickedExploreLink() {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "explore_click", {
app_name: "novodraft",
screen_name: "Home",
});
}
}
async function saveUserData(
authId,
dataValues,
email,
password,
customerId,
subscriptionCreated,
subscriptionPeriodStart,
subscriptionPeriodEnd,
subscriptionId,
isPromotionalMebership = true,
docsAllowedPeMonth = 1
) {
if (!email || !password || !dataValues[0]) {
return;
}
const appUserId = uuidv4();
const firmId = uuidv4();
const fbAuthUid = authId;
const tpe = password;
let plan = "promo plan";
let docsAllowedPerMonth;
docsAllowedPerMonth = 1;
subscriptionId = uuidv4();
plan = plan;
const docsGenerated = 0;
const {
firstName,
lastName,
firm,
telephone,
streetAddress,
city,
state,
zipCode,
barNumber,
practiceArea,
} = dataValues[0];
const userState = stateDataValues.filter((value) => {
if (value.name === state) return value;
});
const tempData = {
docsAllowedPerMonth,
docsGenerated,
appUserId,
fbAuthUid,
firmId,
firstName,
lastName,
firm,
telephone,
streetAddress,
city,
state: userState,
zipCode,
barNumber,
practiceArea,
email,
tpe: tpe,
isPromotionalMebership,
isPromotionalFirstLogin: true,
customerId: customerId,
subscriptionId: subscriptionId,
subscriptionPlan: "flordia-spring-promo-plan",
subscriptionCreated: subscriptionCreated,
subscriptionPeriodStart: subscriptionPeriodStart,
subscriptionPeriodEnd: subscriptionPeriodEnd,
};
function scrubUndef(obj) {
Object.keys(obj).forEach(function (key) {
if (obj[key] === undefined) {
obj[key] = "---";
}
});
return obj;
}
const userData = scrubUndef(tempData);
try {
const usersRef = collection(db, "users");
const res = await setDoc(doc(usersRef, fbAuthUid), userData);
promoSignup();
return res;
} catch (error) {
console.log(`Error saving new user to db: ${error}`);
}
}
async function getFocusData(code) {
try {
const response = await fetch(`${apiUrl}/v1/get-focused-data/${code}`, {
method: "GET",
});
const res = await response.json();
return res;
} catch (err) {
console.log("Error in get rqst:", err);
}
}
async function handleProcessPromoSubscription(code) {
setIsBusy(true);
setShowPromoModal(false);
if (!code) {
return;
}
const userData = await getFocusData(code);
let email;
let password;
if (userData === undefined) {
return;
} else {
email = userData[0].email;
password = userData[0].mspall;
}
if (email && password) {
handlePromoSignup(userData, email, password);
} else {
setIsBusy(false);
setShowPromoModal(false);
}
}
async function handlePromoSignup(dataValues, email, password) {
if (!email || !password) {
return;
}
try {
const customerId = `${uuidv4()}-florida-spring-promo`;
const today = new Date();
const subscriptionCreated = today;
const subscriptionPeriodStart = today;
const subscriptionPeriodEnd = today;
const subscriptionId = uuidv4();
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
dataValues = { ...dataValues };
const isPromotionalMebership = true;
const res = await saveUserData(
user.uid,
dataValues,
email,
password,
customerId,
subscriptionCreated,
subscriptionPeriodStart,
subscriptionPeriodEnd,
subscriptionId,
isPromotionalMebership
);
setIsBusy(false);
navigate("/dashboard");
} catch (error) {
console.log("Error handling request", error, error.message);
setIsBusy(false);
setNotice("Sorry, something went wrong. Please try again.");
}
}
const handleTryItButton = (e) => {
clickedTryItButton();
setShowPromoModal(true);
};
const handleLogin = () => {
navigate("/login");
};
const handleCancelModal = () => {
setShowPromoModal(!showPromoModal);
navigate("/signup");
};
const handleDemo = () => {
const supReq = null;
navigate("/requestpage/supReq");
};
const homeContent = (
<div className="homepage-container">
<section
className="section-1"
style={{
backgroundImage: `url(https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/ee5222a0-7480-43ba-0f14-0ea482f31300/public)`,
backgroundSize: "cover",
}}
>
<div className="first-section-subcontainer">
<div className="row upper-row-one">
<div className="col-md-12 mobile-col-up">
<div className="heading-one-container">
<h1 className="heading-1">Novodraft: Legal AI</h1>
<h1 className="heading-1-mobile">Novodraft</h1>
<h1 className="heading-2-mobile">AI-Assisted Legal Drafting</h1>
<h1 className="heading-2">
Draft documents faster <i>and </i> smarter
</h1>
</div>
<div className="mobile-lower-header-1">
<div className="mobile-lower-header-top">
<div className="mobile-sw-deck">Discovery requests</div>
<div className="mobile-sw-deck"> dramatically fast</div>
<div className="mobile-sw-reptwo">
<div className="ex-arca-two">Draft responses to</div>
<div className="ex-arca-two">demands in minutes</div>
</div>
</div>
<div className="mobile-lower-header-bottom">
<div className="mobile-lower-header-bottom-up"></div>
<div className="mobile-lower-header-bottom-down">
<Button
className="primary-button homepage-button"
labelText="Try It For Free"
onClick={handleTryItButton}
/>
</div>
</div>
</div>
<p className="text-block-1">
Discovery requests dramatically fast responses ready in
minutes.
</p>
<p className="text-block-two">
Reclaim hours every week. Spend more time growing your practice,
or just get home in time for dinner.
</p>
<div className="try-it-button-box">
<Button
className="primary-button homepage-button"
labelText="Try It For Free"
onClick={handleTryItButton}
/>
</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="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/5f0f6f11-bb56-4f03-b05e-189d4f1a5600/public"
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 developed by people that understand what you need.
</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"
onClick={() => clickedExploreLink()}
>
<div className="explore-link">
Explore Novodraft
<img src={arrow} className="img-1" alt="" />
</div>
</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>
<h2 className="section-three-header-mobile">
Novo Drafting Technology
</h2>
<p className="section-three-subhead">
Created by litigators 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="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/5ff0ba9e-298f-4ee1-88d4-e19a44a00900/public"
alt="ai card"
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 interrogatories and requests for
production/admissions leveraging AI.
</p>
<p className="card-texts">
Edit or add special language with tags. Concise yet
comprehensive demands, done in minutes.
</p>
</div>
</div>
</div>
{/*********** CARD TWO *********** */}
<div className="home-card">
<div className="card-image-container">
<img
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/7a6dd50e-4d8f-4068-61b2-634dc5603f00/public"
alt="scales image"
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">
Use Novodraft AI to quickly draft responses including
compelling arguments about what you withhold from disclosure.
Finish in minutes with a downloadable .docx file.
</p>
</div>
</div>
</div>
{/*********** CARD THREE *********** */}
<div className="home-card">
<div className="card-image-container">
<img
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/00667aa1-f847-430c-7a5e-d96abeed3300/public"
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 dashboard displays important deadlines, alerts, and can
be configured to auto-draft documents with reminders for tasks
such as service or hearings.
</p>
</div>
</div>
</div>
</div>
</section>
<section className="section-5 py-5">
<div
className="home-test-container"
style={{
backgroundImage: `url(https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/209dcf14-c086-42b4-ca72-f058d9ad1b00/public)`,
backgroundSize: "cover",
}}
>
<div className="row">
<div className="col-md-6">
<div className="bottom-header-box">
<h4 className="heading-6">
Questions about our services? <br></br>
We're here to answer!
</h4>
<h4 className="heading-6-mobile">
Questions about our products and services?
</h4>
<h4 className="heading-6-mobile">We're here to answer!</h4>
</div>
<h6 className="join-heading">
Novodraft will revolutionize your practice.
</h6>
<h6 className="join-heading">
Request a demo - let us show you how.
</h6>
<h6 className="join-heading-mobile">
Revolutionize your practice.
</h6>
<h6 className="join-heading-mobile">Let us show you how.</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 login
</button>
</div>
</div>
</div>
</section>
{showPromoModal ? (
<ConfirmModal
titleText="Enter Code"
onCancel={handleCancelModal}
buttonLabelText="Enter Code"
isPromoModal={true}
onConfirm={handleProcessPromoSubscription}
cancelButtonText="I don't have a code"
/>
) : (
<></>
)}
</div>
);
return isBusy ? (
<LoadingSpinner message={""} loaderType="MoonLoader" />
) : (
homeContent
);
};
export default HomePage;

View File

@@ -0,0 +1,622 @@
import { useEffect, useState } from "react";
import { collection, setDoc, doc } from "firebase/firestore";
import { db, auth } from "../../firebase";
import Button from "../../pageElements/Button";
import { v4 as uuidv4 } from "uuid";
import arrow from "../../Assets/Images/Arrow.png";
import { useNavigate } from "react-router-dom";
import ConfirmModal from "../Modals/ConfirmModal";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { stateDataValues } from "../../Constants/Fields/SignupFields";
import "../../styles/homepage-b.scss";
import LoadingSpinner from "../../pageElements/LoadingSpinner";
import { StarFill } from "react-bootstrap-icons";
const HomePageB = () => {
const navigate = useNavigate();
const [showPromoModal, setShowPromoModal] = useState(false);
const [isBusy, setIsBusy] = useState(false);
const [notice, setNotice] = useState("");
const heroString =
"Interrogatories, requests for admissions/production + motions. ";
const heroString2 = "Done in < 10 minutes. For $9.50/week.";
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
useEffect(() => {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "screen_view", {
app_name: "novodraft",
screen_name: "Home",
});
}
}, []);
function clickedTryItButton() {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "try_it_click", {
app_name: "novodraft",
screen_name: "Home",
});
}
}
function promoSignup() {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "promo_sign_up", {
send_to: "G-J6SQL5DF6V",
});
}
}
function clickedExploreLink() {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "explore_click", {
app_name: "novodraft",
screen_name: "Home",
});
}
}
async function saveUserData(
authId,
dataValues,
email,
password,
customerId,
subscriptionCreated,
subscriptionPeriodStart,
subscriptionPeriodEnd,
subscriptionId,
isPromotionalMebership = true,
docsAllowedPeMonth = 99999
) {
if (!email || !password || !dataValues[0]) {
return;
}
const appUserId = uuidv4();
const firmId = uuidv4();
const fbAuthUid = authId;
const tpe = password;
let plan = "promo plan";
let docsAllowedPerMonth;
docsAllowedPerMonth = 1;
subscriptionId = uuidv4();
plan = plan;
const docsGenerated = 0;
const {
firstName,
lastName,
firm,
telephone,
streetAddress,
city,
state,
zipCode,
barNumber,
practiceArea,
} = dataValues[0];
const userState = stateDataValues.filter((value) => {
if (value.name === state) return value;
});
const tempData = {
docsAllowedPerMonth,
docsGenerated,
appUserId,
fbAuthUid,
firmId,
firstName,
lastName,
firm,
telephone,
streetAddress,
city,
state: userState,
zipCode,
barNumber,
practiceArea,
email,
tpe: tpe,
isPromotionalMebership,
isPromotionalFirstLogin: true,
customerId: customerId,
subscriptionId: subscriptionId,
subscriptionPlan: "flordia-spring-promo-plan",
subscriptionCreated: subscriptionCreated,
subscriptionPeriodStart: subscriptionPeriodStart,
subscriptionPeriodEnd: subscriptionPeriodEnd,
};
function scrubUndef(obj) {
Object.keys(obj).forEach(function (key) {
if (obj[key] === undefined) {
obj[key] = "---";
}
});
return obj;
}
const userData = scrubUndef(tempData);
try {
const usersRef = collection(db, "users");
const res = await setDoc(doc(usersRef, fbAuthUid), userData);
promoSignup();
return res;
} catch (error) {
console.log(`Error saving new user to db: ${error}`);
}
}
async function getFocusData(code) {
try {
const response = await fetch(`${apiUrl}/v1/get-focused-data/${code}`, {
method: "GET",
});
const res = await response.json();
return res;
} catch (err) {
console.log("Error in get rqst:", err);
}
}
async function handleProcessPromoSubscription(code) {
setIsBusy(true);
setShowPromoModal(false);
if (!code) {
return;
}
const userData = await getFocusData(code);
let email;
let password;
if (userData === undefined) {
return;
} else {
email = userData[0].email;
password = userData[0].mspall;
}
if (email && password) {
handlePromoSignup(userData, email, password);
} else {
setIsBusy(false);
setShowPromoModal(false);
}
}
async function handlePromoSignup(dataValues, email, password) {
if (!email || !password) {
return;
}
try {
const customerId = `${uuidv4()}-florida-spring-promo`;
const today = new Date();
const subscriptionCreated = today;
const subscriptionPeriodStart = today;
const subscriptionPeriodEnd = today;
const subscriptionId = uuidv4();
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
dataValues = { ...dataValues };
const isPromotionalMebership = true;
const res = await saveUserData(
user.uid,
dataValues,
email,
password,
customerId,
subscriptionCreated,
subscriptionPeriodStart,
subscriptionPeriodEnd,
subscriptionId,
isPromotionalMebership
);
setIsBusy(false);
navigate("/dashboard");
} catch (error) {
console.log("Error handling request", error, error.message);
setIsBusy(false);
setNotice("Sorry, something went wrong. Please try again.");
}
}
const handleTryItButton = (e) => {
clickedTryItButton();
setShowPromoModal(true);
};
const handleLogin = () => {
navigate("/login");
};
const handleCancelModal = () => {
setShowPromoModal(!showPromoModal);
navigate("/signup");
};
const handleDemo = () => {
const supReq = null;
navigate("/requestpage/supReq");
};
const homeContent = (
<div className="homepage-container">
<section className="section-one">
<div
className="hero-imgbox"
style={{
backgroundImage: `url(https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/038dfbe4-00e9-41f9-6b70-09e8e87fa600/public)`,
backgroundSize: "cover",
borderRadius: "20px",
}}
>
<div className="hero-middle">
<h1 className="hero-middle-header">{heroString}</h1>
<h1 className="hero-middle-header">{heroString2}</h1>
</div>
<div className="hero-button-box">
<button className="button-2" onClick={handleTryItButton}>
Start A Free Trial
</button>
<button className="button-2" onClick={handleDemo}>
Request a demo
</button>
</div>
<div className="hero-text-box">
<div className="hero-text">
Reclaim hours every week. Spend more time growing your practice,
or just get home in time for dinner.
</div>
<div className="arrow-container">
<div className="arrow-sub-left"></div>
<div className="arrow-sub-right">
<div className="arrow-left"></div>
<div className="arrow-right">
<img
className="testi-arrow"
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/046d4940-35e7-481f-e3c1-259cc32d3800/public"
></img>
</div>
</div>
</div>
<div className="hero-testi-box">
<div className="hero-testi-left">
<StarFill
style={{
color: "#FFD700",
height: "20px",
width: "20px",
marginRight: "6px",
}}
/>
<StarFill
style={{
color: "#FFD700",
height: "20px",
width: "20px",
marginRight: "6px",
}}
/>
<StarFill
style={{
color: "#FFD700",
height: "20px",
width: "20px",
marginRight: "6px",
}}
/>
<StarFill
style={{
color: "#FFD700",
height: "20px",
width: "20px",
marginRight: "6px",
}}
/>
<StarFill
style={{ color: "#FFD700", height: "20px", width: "20px" }}
/>
<div className="review-box">
Used by over 1,100 Novodrafters, and counting.
</div>
</div>
<div className="hero-testi-right">
<div className="hero-testi-right-inner">
<img
className="hero-testi-face"
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/bdb625eb-2949-4416-f675-6660e1a04f00/public"
></img>
</div>
</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">
<div className="laptop-image-container">
<img
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/5f0f6f11-bb56-4f03-b05e-189d4f1a5600/public"
alt="image of a laptop computer"
className="img-2"
/>
</div>
</div>
</div>
<div className="home-col-50">
<div className="pad-2">
<h2 className="section-three-header">
Cut through the rote - focus on your priorities.
</h2>
<p className="section-two-body">
Novodraft is not a chatbot, or an AI assistant. It is a
heuristic framework that leverages AI to free you from reciting
boilerplate, "raised-or-waived," "gotcha" arguments and
objections.
</p>
<p className="section-two-body">
Novodraft generates discovery requests and responses within
precisely-defined natural language parameters. It even applies
CRAC structure, where appropriate.
</p>
<p className="section-two-body">
This allows you to spend more time on key, dispositive issues.
The issues you know will carry the day.
</p>
<p className="section-two-body">
Most importantly, Novdraft observes and incorporates your unique
preferences. Over time, you will find yourself more able to
"lean in" on what matters the most.
</p>
<p className="section-two-body"></p>
<div
className="home-link-box"
onClick={() => clickedExploreLink()}
></div>
</div>
</div>
</div>
</section>
{/* Section 3 */}
<section className="section-3">
<div className="section-three-header-box">
<h2 className="section-three-header">
AI-Enhanced Legal Drafting Technology{" "}
</h2>
<p className="section-three-subhead"></p>
</div>
<div className="card-section-container">
{/*********** CARD ONE *********** */}
<div className="home-card">
<div className="card-image-container">
<img
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/5ff0ba9e-298f-4ee1-88d4-e19a44a00900/public"
alt="ai hand"
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 interrogatories and requests for
production/admissions leveraging AI.
</p>
<p className="card-texts">
Edit or add special language with tags. Concise yet
comprehensive demands, done in minutes.
</p>
</div>
</div>
</div>
{/*********** CARD TWO *********** */}
<div className="home-card">
<div className="card-image-container">
<img
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/7a6dd50e-4d8f-4068-61b2-634dc5603f00/public"
alt="scales image"
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">
Use Novodraft AI to quickly draft responses including
compelling arguments about what you withhold from disclosure.
Finish in minutes with a downloadable .docx file.
</p>
</div>
</div>
</div>
{/*********** CARD THREE *********** */}
<div className="home-card">
<div className="card-image-container">
<img
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/00667aa1-f847-430c-7a5e-d96abeed3300/public"
alt=""
className="card-image"
/>
</div>
<div className="card-body">
<div className="card-upper-box">
<div className="home-card-title">
<h3 className="foo-card-title">Discovery Motions</h3>
<div className="home-card-divider"></div>
</div>
</div>
<div className="home-card-text-box">
<p className="card-texts">
Motions to compel disclosure. Fully editable/customizable to
your individual preferences. Automtically formatted and ready
to serve in 10 minutes or less.
</p>
</div>
</div>
</div>
</div>
</section>
<section className="section-5 py-5">
<div
className="home-test-container"
style={{
backgroundImage: `url(https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/209dcf14-c086-42b4-ca72-f058d9ad1b00/public)`,
backgroundSize: "cover",
}}
>
<div className="row">
<div className="col-md-6">
<div className="bottom-header-box">
<h4 className="heading-6">
Questions about our services? <br></br>
We're here to answer!
</h4>
<h4 className="heading-6-mobile">
Questions about our products and services?
</h4>
<h4 className="heading-6-mobile">We're here to answer!</h4>
</div>
<h6 className="join-heading">
Novodraft will revolutionize your practice.
</h6>
<h6 className="join-heading">
Request a demo - let us show you how.
</h6>
<h6 className="join-heading-mobile">
Revolutionize your practice.
</h6>
<h6 className="join-heading-mobile">Let us show you how.</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}>
Start a free trial
</button>
</div>
</div>
</div>
</section>
{showPromoModal ? (
<ConfirmModal
titleText="Enter Code"
onCancel={handleCancelModal}
buttonLabelText="Enter Code"
isPromoModal={true}
onConfirm={handleProcessPromoSubscription}
cancelButtonText="I don't have a code"
/>
) : (
<></>
)}
</div>
);
return isBusy ? (
<LoadingSpinner message={""} loaderType="MoonLoader" />
) : (
homeContent
);
};
export default HomePageB;
/*
<div className="first-section-subcontainer">
<div className="row upper-row-one">
<div className="col-md-12 mobile-col-up">
<div className="heading-one-container">
<h1 className="heading-1">Novodraft: Legal AI</h1>
<h1 className="heading-1-mobile">Novodraft</h1>
<h1 className="heading-2-mobile">AI-Assisted Legal Drafting</h1>
<h1 className="heading-2">
Draft documents faster <i>and </i> smarter
</h1>
</div>
<div className="mobile-lower-header-1">
<div className="mobile-lower-header-top">
<div className="mobile-sw-deck">Discovery requests</div>
<div className="mobile-sw-deck"> dramatically fast</div>
<div className="mobile-sw-reptwo">
<div className="ex-arca-two">Draft responses to</div>
<div className="ex-arca-two">demands in minutes</div>
</div>
</div>
<div className="mobile-lower-header-bottom">
<div className="mobile-lower-header-bottom-up"></div>
<div className="mobile-lower-header-bottom-down">
<Button
className="primary-button homepage-button"
labelText="Try It For Free"
onClick={handleTryItButton}
/>
</div>
</div>
</div>
<p className="text-block-1">
Discovery requests dramatically fast responses ready in
minutes.
</p>
<p className="text-block-two">
Reclaim hours every week. Spend more time growing your practice,
or just get home in time for dinner.
</p>
<div className="try-it-button-box">
<Button
className="primary-button homepage-button"
labelText="Try It For Free"
onClick={handleTryItButton}
/>
</div>
</div>
</div>
<div className="explore-link">
Explore Novodraft
<img src={arrow} className="img-1" alt="" />
</div>
*/

View File

@@ -0,0 +1,32 @@
import { useContext } from "react";
import { AppContext } from "../../Hooks/useContext/appContext";
import { Steps, OutgoingSteps } from "../../Constants/Copy/howToSteps.js";
import "../../styles/howTo-page.scss";
const HowToPage = () => {
const { appState } = useContext(AppContext);
const { group } = appState;
if (!group) return null;
return (
<>
<div>
<Steps />
<OutgoingSteps />
</div>
</>
);
};
export default HowToPage;
/*
<div className="how-to-image-wrapper">
<img className="how-to-image" src={howto}></img>
</div>
<div className="how-to-image-wrapper">
<img className="footer-logo" src={logoWhite}></img>
</div>
*/

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,134 @@
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 {
console.log("auth, email, password", auth, email, password);
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
// Signed in
userCredential?.user && navigate("/dashboard");
} catch (error) {
console.log("error", 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();
//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,67 @@
import { ExclamationTriangle } from "react-bootstrap-icons";
const MobileContent = ({ isDashboard }) => {
const copy = isDashboard ? (
<>
<div>
<p className="mobile-text-dashboard">
Novodraft is a desktop application. The functionailty of your
Dashboard in mobile mode is limited to displaying stats and important
notifications.
</p>
<p className="mobile-text-dashboard">
Please login on your desktop device to use all of the features of your
Dashboard.
</p>
</div>
</>
) : (
"Novodraft is a desktop application. Please login on your desktop device to use this feature."
);
return (
<>
<div className="mobile-content-container">
{!isDashboard ? (
<>
<div className="mobile-divider"></div>
<div className="features-top-mobile-img">
<img
className="features-image-one"
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/79eafada-1517-43a6-99f1-d3e2b0da8200/public"
/>
</div>
</>
) : (
<>
<div>
<div className="notication-container">
<strong>Notification Center</strong>
<div className="notication-text-container">
<p>
<ExclamationTriangle
style={{ color: "red", marginBottom: "5px" }}
/>
</p>
<p className="notification-text">No new notifications</p>
</div>
</div>
</div>
</>
)}
<div className="lower-foo">
{isDashboard ? (
<>
<div className="mobile-divider"></div>
<div className="mobile-info-heading-dashboard">{copy}</div>
<div className="mobile-divider"></div>
</>
) : (
<div className="mobile-info-heading">{copy}</div>
)}
</div>
</div>
</>
);
};
export default MobileContent;

View File

@@ -0,0 +1,105 @@
import { useState } from "react";
import Button from "../../pageElements/Button";
import Modal from "react-bootstrap/Modal";
import TextInput from "../../pageElements/TextInput";
import { ExclamationTriangle } from "react-bootstrap-icons";
import { db } from "../../firebase";
const ConfirmModal = ({
onCancel,
onConfirm,
buttonLabelText,
modalText,
isBusy,
isReport,
appUserId,
caseId,
documentId,
issueText,
setIssueText,
reportDoc,
titleText,
isPromoModal,
cancelButtonText,
}) => {
const [inputValue, setInputValue] = useState("");
const [codeError, setCodeError] = useState("");
const title = titleText ? titleText : "Confirmation";
const handleConfirm = (code) => {
if (!code) {
setCodeError("Please enter a code");
return;
}
onConfirm(code);
};
return (
<Modal show="true" onHide={onCancel} size="lg">
<Modal.Header closeButton>
{isReport ? (
<Modal.Title>Report an issue</Modal.Title>
) : (
<Modal.Title>
{titleText && !isPromoModal ? (
<ExclamationTriangle
style={{
color: "red",
marginRight: "8px",
marginBottom: "6px",
}}
/>
) : (
<></>
)}
{title}
</Modal.Title>
)}
</Modal.Header>
<Modal.Body>
<span>{modalText}</span>
{isReport || isPromoModal ? (
<div style={{ marginTop: "16px" }}>
<TextInput
key={"captionTwo"}
name={"captionTwo"}
value={inputValue}
message={"data.captionTwo.message"}
onChange={(e) => setInputValue(e.target.value)}
label={
inputValue
? ""
: isPromoModal
? "Paste or type your code"
: "Description of issue"
}
/>
<div className="confirm-error-box">
{codeError ? (
<p className="confirm-error-text">{codeError}</p>
) : (
<></>
)}
</div>
</div>
) : (
<></>
)}
</Modal.Body>
<Modal.Footer>
<Button
disabled={isBusy}
className="secondary-button"
onClick={onCancel}
labelText={cancelButtonText ? cancelButtonText : "Cancel"}
/>
<Button
disabled={isBusy}
className="primary-button"
onClick={() => handleConfirm(inputValue)}
labelText={buttonLabelText}
/>
</Modal.Footer>
</Modal>
);
};
export default ConfirmModal;

View File

@@ -0,0 +1,523 @@
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 { collection, setDoc, updateDoc, doc } from "firebase/firestore";
import { AppContext } from "../../Hooks/useContext/appContext.js";
import { db } from "../../firebase";
import { objectMap } from "../../Utils/Object";
import { createCaseFields as fields } from "../../Constants/Fields/CreateCaseFields.js";
import {
floridaCourts,
michiganCourts,
newYorkCounties,
} from "../../Constants/Fields/CreateCaseFields.js";
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 [isBusy, setIsBusy] = useState(false);
const [venueArray, setVenueArray] = useState();
const [newCaseId, setNewCaseId] = useState();
const state = group?.state[0].code ? group?.state[0].code : null;
const caseNumCopy = state === "ny" ? "Index Number" : "Case Number";
const [data, setData] = useState(
getFormDataDefaults(fields, isEditing ? caseData : undefined)
);
const courtsList = state === "mi" ? michiganCourts : floridaCourts;
function selectVenueValues(data) {
const result = courtsList.filter((court) => {
let val;
const target = data?.jurisdiction?.value?.split(" ")[0];
if (court?.circuit === target) {
return court?.counties;
}
});
const count = result[0]?.counties;
return count;
}
const handleChangeInput = (e, name) => {
let venArr;
const newData = handleFormDataChange(e, name, data, fields);
if (state === "fl" || state === "mi") {
venArr = selectVenueValues(newData);
}
setVenueArray(venArr);
if (newData !== null) {
setData(newData);
}
};
const validateData = () => {
const newData = getValidatedFormData(data, fields);
const hasErrors = isFormDataHasErrors(newData);
setData(newData);
return hasErrors ? null : objectMap(({ value }) => value, newData);
};
async function saveCaseData(dataValues) {
if (isBusy) {
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);
return caseId;
} catch (err) {
console.log("Error creating case:", err);
}
}
setIsBusy(false);
}
const stateOfCase = "NY";
const handleClose = () => {
if (!isBusy) {
setShowModal(false);
}
};
const handleCreateCase = (e) => {
e.preventDefault();
const dataValues = validateData();
let caseId;
if (dataValues === null) {
return;
} else {
try {
saveCaseData(dataValues).then((res) => {
const caseId = res;
navigate(`/casetype/${caseId}`);
});
} catch (err) {
console.log(err);
}
}
};
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">{caseNumCopy}</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
: caseNumCopy
: ""
}
/>
</Col>
<Col className="mb-3">
{isEditing ? (
<div className="edit-modal-label">Jurisdiction</div>
) : (
<></>
)}
<TextInput
key={"jurisdiction"}
name={"jurisdiction"}
state={state}
value={data.jurisdiction.value}
values={courtsList}
error={data.jurisdiction.error}
message={data.jurisdiction.message}
type={state === "fl" || state === "mi" ? "select" : "text"}
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">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">Venue</div> : <></>}
<TextInput
key={"venue"}
name={"venue"}
value={data.venue.value}
error={data.venue.error}
type={state === "fl" || state === "mi" ? "select" : "text"}
values={venueArray}
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">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>
</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={handleCreateCase}
labelText="Save Case"
/>
</Modal.Footer>
</Modal>
);
};
export default CreateModal;
/*
<Col className="mb-3">
{isEditing ? (
<div className="edit-modal-label">Contact name</div>
) : (
<></>
)}
<TextInput
key={"contactName"}
name={"contactName"}
value={data.contactName.value}
error={data.contactName.error}
message={data.contactName.message}
disabled={isBusy}
onChange={(e) => handleChangeInput(e, "contactName")}
label={
data.contactName.value.length === 0
? isEditing
? caseData.contactName
: "Contact Name"
: ""
}
/>
</Col>
<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>
<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>
<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>
*/

View File

@@ -0,0 +1,184 @@
import { useMemo } from "react";
import Button from "../../pageElements/Button";
import Modal from "react-bootstrap/Modal";
import TextInput from "../../pageElements/TextInput";
import { splitEvery } from "../../Utils/Array";
import Col from "react-bootstrap/Col";
import Form from "react-bootstrap/Form";
import Row from "react-bootstrap/Row";
import "../../styles/modals.scss";
const PaymentModal = ({
isBusy,
paymentData,
paymentfields,
handleChangePaymentInput,
handleSignUp,
setShowPaymentModal,
handleSignup,
selectedPlan,
isAnnual,
fieldsChunkSize,
numberOfAccountsToAdd,
}) => {
const monthlySubscriptionPriceToday = selectedPlan[0].pricing[0].amount;
const annualSubscriptionPriceToday = selectedPlan[0].pricing[1].amount * 12;
const additionalAccountsMonthlyPriceToday = selectedPlan[0].pricing[2].amount
? selectedPlan[0].pricing[2].amount * numberOfAccountsToAdd
: null;
const additionalAccountsAnnualPriceToday =
additionalAccountsMonthlyPriceToday * 12;
const extraAccountsPrice = isAnnual
? additionalAccountsAnnualPriceToday
? additionalAccountsAnnualPriceToday
: 0
: additionalAccountsMonthlyPriceToday
? additionalAccountsMonthlyPriceToday
: 0;
const showAdditionaLAccountsView =
(selectedPlan[0]?.value === "partner" && numberOfAccountsToAdd > 0) ||
(selectedPlan[0]?.value === "seniorPartner" && numberOfAccountsToAdd > 0);
const planName = selectedPlan[0].name ? selectedPlan[0].name : null;
const planValue = selectedPlan[0].value ? selectedPlan[0].value : null;
const billingPeriod =
planValue === "ofCounsel"
? "Single Purchase"
: isAnnual
? "Annually"
: "Monthly";
const amountToday =
planValue === "ofCounsel"
? 59
: isAnnual
? annualSubscriptionPriceToday
: monthlySubscriptionPriceToday;
const totalToday =
planValue === "ofCounsel" ? 59 : amountToday + extraAccountsPrice;
console.log("selectedPlan in modal", selectedPlan);
const isCounsel = planValue === "ofCounsel" ? true : false;
const handleSignupClick = () => {
//clear fields
handleSignup();
};
return (
<Modal show="true" size="lg">
<Modal.Header className="payment-modal-header" closeButton>
<Modal.Title>Enter Payment Information</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="payment-summary-header">Order Summary</div>
<div className="order-summary-container">
<div className="paymentmodal-order-summarybox">
<div className="payment-modal-text-wrapper">
<div className="paymodal-summary-right">Subscription Type:</div>
<div className="paymodal-summary-left">{planName}</div>
</div>
<div className="payment-modal-text-wrapper">
<div className="paymodal-summary-right">
Billing Period/Amount:
</div>
<div className="paymodal-summary-left">
{billingPeriod} - ${amountToday}
</div>
</div>
{numberOfAccountsToAdd !== undefined &&
numberOfAccountsToAdd > 0 ? (
<div className="payment-modal-text-wrapper">
<div className="paymodal-summary-right">
{numberOfAccountsToAdd} Additional{" "}
{numberOfAccountsToAdd > 1 ? `accounts:` : `account:`}
</div>
<div className="paymodal-summary-left">
{billingPeriod} - ${extraAccountsPrice}
</div>
</div>
) : (
<></>
)}
<div className="payment-modal-text-wrapper">
<div className="paymodal-summary-right">Total today:</div>
<div className="paymodal-summary-left">${totalToday}</div>
</div>
{isAnnual && !isCounsel ? (
<div className="payment-modal-text-wrapper">
<div className="paymodal-summary-right"></div>
<div className="paymodal-summary-left-bonus">
Includes 15% discount for convenient annual billing
</div>
</div>
) : (
<></>
)}
</div>
</div>
<Form className="payment-form">
{splitEvery(Object.keys(paymentfields), fieldsChunkSize).map(
(names, j) => (
<Row key={`row${j}-modal`} className="payment-order-row">
{j === 2 ? (
<div className="exp-date-hearder">Card Expiration Date</div>
) : (
<></>
)}
{names.map((name, i) => (
<Col
key={`${name}${i + fieldsChunkSize * j}-modal`}
className={j === 2 ? "mb3 short-column" : "mb-3"}
>
<TextInput
key={`${name}-modal-input`}
name={name}
value={paymentData[name].value}
onChange={(e) => handleChangePaymentInput(e, name)}
error={paymentData[name].error}
message={paymentData[name].message}
label={
paymentData[name].value.length === 0
? paymentfields[name].label
: ""
}
type={paymentfields[name].type}
values={paymentfields[name].values}
disabled={isBusy}
inputClassName={
j === 2
? "skinny-input"
: j === 1 && i === 1
? "skinny-input"
: null
}
/>
</Col>
))}
</Row>
)
)}
</Form>
</Modal.Body>
<Modal.Footer>
<Button
disabled={isBusy}
className="secondary-button"
onClick={() => setShowPaymentModal(false)}
labelText="Cancel"
/>
<Button
disabled={isBusy}
className="primary-button"
onClick={handleSignupClick}
labelText="Signup"
/>
</Modal.Footer>
</Modal>
);
};
export default PaymentModal;

View File

@@ -0,0 +1,376 @@
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 Radiogroup from "../../pageElements/Radiogroup";
import {
collection,
setDoc,
doc,
updateDoc,
increment,
} from "firebase/firestore";
import { db } from "../../firebase";
import { v4 as uuidv4 } from "uuid";
import TextInput from "../../pageElements/TextInput";
import { ExclamationTriangle } from "react-bootstrap-icons";
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,
setIsLoading,
clientPosition,
fbUserId,
uploadType,
} = 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 [radioValue, setRadioValue] = useState(uploadType);
const [radioValueError, setRadioValueError] = useState("");
const [showFileDetails, setShowFileDetails] = useState("");
const [titleError, setShowTitleError] = useState("");
const [docError, setShowDocError] = useState("");
const fileTypes = ["PDF"];
const fileName = fileToParse ? fileToParse.name : undefined;
const currentType =
uploadType === "complaint" ? "complaint" : "discovery request";
const otherType =
uploadType === "complaint" ? "discovery request" : "complaint";
let docType;
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
const radioOptions = [
{
name: "Discovery request",
label: "Discovery request",
value: "discovery-request",
},
{ name: "Complaint", label: "Complaint", value: "complaint" },
];
const handleSetRadioValue = (e) => {
setRadioValue(e.value);
};
async function updateUserAccountDocsGenerated(fbUserId) {
await updateDoc(doc(db, "users", fbUserId), {
docsGenerated: increment(1),
});
}
async function saveToDb(uuidName) {
const createdAt = new Date();
let respGenVal;
if (radioValue === "complaint") {
respGenVal = 1;
} else {
respGenVal = 0;
}
const data = {
ownerId: appUserId,
createdAt: createdAt,
documentName: `${uuidName}.pdf`,
docDescription: docDescription,
dateServed: dateServed,
docTitle: docTitle,
parentCaseId: caseId,
parentCaseName: `${caption} v. ${captionTwo}`,
parentCaseNumber: caseNumber,
parentCaseJurisdiction: jurisdiction,
responseGenerations: respGenVal,
clientPosition: clientPosition,
};
if (radioValue === "complaint") {
docType = "interrogatories-out";
data["docType"] = docType;
updateUserAccountDocsGenerated(fbUserId);
}
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) {
if (!clientPosition) {
return;
}
try {
if (radioValue.toLowerCase() === "complaint") {
const response = await fetch(`${apiUrl}/v1/parse-new-compdoc`, {
method: "POST",
body: file,
});
const res = response;
return res;
} else {
const response = await fetch(`${apiUrl}/v1/parse-new-req-doc`, {
method: "POST",
body: file,
});
const res = response;
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);
response["uuidName"] = uuidName;
response["res"] = res;
return response;
}
async function handleUpload() {
if (!docTitle && !fileToParse && !radioValue) {
setShowDocError("Please select a document to upload.");
setShowTitleError("Please enter a document title");
setRadioValueError("Please select document type");
return;
} else if (!docTitle) {
setShowTitleError("Please enter a document title");
return;
} else if (!fileToParse) {
setShowDocError("Please select a document to upload.");
return;
}
setIsLoading(true);
try {
const response = await processFile();
saveToDb(response.uuidName);
const docId = response.uuidName;
setTimeout(handleSuccess, 10000, docId, radioValue);
return;
} catch (err) {
console.log("err", err);
}
}
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>Upload {currentType}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="modal-header-text-wrapper">
<p className="upload-modal-header-text">
1, Enter document title, (i.e. Plaintiff's Interrogatories). 2.
Click or drag and drop the .psf file.
</p>
<p className="upload-modal-header-text">
The document created will be associated with{" "}
<em>
{caption} v. {captionTwo}
</em>
{caseNumber ? `, ${caseNumber}` : ""}.
</p>
<p className="upload-modal-header-text">
If that is not the correct case: 1. click cancel 2. click back, then
3. select correct case from active cases.
</p>
<p className="upload-modal-header-text red">
<span className="upload-header-text">
<ExclamationTriangle
style={{
color: "red",
marginBottom: "6px",
marginRight: "8px",
}}
/>
YOU ARE UPLOADING A {currentType.toUpperCase()}.
<p className="upload-modal-header-text indent">
If you intended to upload a {otherType} - click "cancel", then
select {otherType}
</p>
</span>
</p>
</div>
<Form>
<Row>
<Col className="mb-3">
<TextInput
key={"title"}
name={"title"}
value={docTitle}
onChange={handleTitleInput}
label={
docTitle
? ""
: `${
currentType.split("")[0].toUpperCase() +
currentType.substring(1, 20)
} title`
}
error={!!titleError}
message={titleError}
/>
</Col>
<Col className="mb-3">
<TextInput
key={"description"}
name={"description"}
value={docDescription}
onChange={handleDescInput}
label={`${
currentType.split("")[0].toUpperCase() +
currentType.substring(1, 20)
} description`}
/>
</Col>
</Row>
<Row>
<Col className="mb-3">
<TextInput
key={"date"}
name={"date"}
value={dateServed}
onChange={handleDateInput}
label={
dateServed
? ""
: `${
currentType.split("")[0].toUpperCase() +
currentType.substring(1, 20)
} date served`
}
/>
</Col>
<Col>
<div></div>
</Col>
</Row>
<Row className="file-upload-row">
<Col className="mb-3">
<Form.Label>Select {currentType}</Form.Label>
<div className="file-uploader">
<FileUploader
multiple={true}
handleChange={handleFileChange}
name="file"
types={fileTypes}
>
<div className={fileToParse ? "drag-box filled" : "drag-box"}>
{fileToParse
? fileToParse[0]?.name
: "Click to select 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(radioValue)}
/>
</Modal.Footer>
</Modal>
);
};
export default UploadModal;
/*
<Radiogroup
title="Document type"
options={radioOptions}
value={radioValue}
radioError={radioValueError}
onClick={(e) => handleSetRadioValue(e, "documentType")}
/>
*/

View File

@@ -0,0 +1,150 @@
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(false);
const [isMobile, setIsMobile] = useState(window.innerWidth < 440);
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: "https://www.novodraft.ai/blog/" },
]
: [
{ text: "Login", link: "login" },
{ text: "Features", link: "features" },
{ text: "Signup", link: "signup" },
{ text: "Blog", link: "https://www.novodraft.ai/blog/" },
];
function navToggle() {
setMenuOpen(!menuOpen);
}
function handleClick(link) {
/*
if (link === "signup") {
return;
}
*/
const _link = link.toLowerCase();
if (_link === "https://www.novodraft.ai/blog/") {
window.open(_link, "_blank");
return;
}
if (_link === undefined) {
_link = "";
}
navigate(`/${_link}`);
}
function packUpAndLeave(link) {
navToggle();
handleClick(link);
}
function special() {
navigate("/");
if (menuOpen) {
setMenuOpen(false);
}
}
async function handleLogout() {
SignOutUser();
}
return (
<div className="navbar-wrapper">
<Navbar fixed="top" className="navbar-main">
<div className="logo-container">
<a onClick={() => special("")}>
<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}
navToggle={navToggle}
packUpAndLeave={packUpAndLeave}
token={token}
/>
</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,98 @@
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, packUpAndLeave, token } = props;
const mobileMainHeadingClass = menuOpen
? "mobileMainHeadingShow"
: "mobile-main-heading-hide";
const mobileHeadingWrapperClass = menuOpen
? "mobile-heading-wrapper1"
: "mobile-heading-wrapper";
return (
<div className={mobileMainHeadingClass}>
<div className={mobileHeadingWrapperClass}>
<div className="mobileHeadingClose">
<div className="mobileHeadingCloseInner">
<a className="mobileHeadingButton" onClick={navToggle}></a>
</div>
</div>
<div className="mobileHeadingLinkWrap">
{!token ? (
<div className="mobile-info-box">
<div className="contact-detail-one">
<a
className="contact-link"
onClick={() => packUpAndLeave("login")}
>
Login
</a>
</div>
<div className="contact-detail-two">
<a
className="contact-link-two"
onClick={() => packUpAndLeave("features")}
>
Features
</a>
</div>
<div className="contact-detail-three">
<a
className="nav-link-three"
onClick={() => packUpAndLeave("signup")}
>
Signup
</a>
</div>
<div className="contact-detail-one">
<a className="contact-link" onClick={() => packUpAndLeave("")}>
Blog
</a>
</div>
</div>
) : (
<div className="mobile-info-box">
<div className="contact-detail-one">
<a
className="contact-link"
onClick={() => packUpAndLeave("dashboard")}
>
Dashboard
</a>
</div>
<div className="contact-detail-twotwo">
<a
className="contact-link-twotwo"
onClick={() => packUpAndLeave("cases")}
>
Cases
</a>
</div>
<div className="contact-detail-threethree">
<a
className="nav-link-threthree"
onClick={() => packUpAndLeave("documents")}
>
Documents
</a>
</div>
<div className="contact-detail-one">
<a
className="contact-linklink"
onClick={() => packUpAndLeave("")}
>
Blog
</a>
</div>
</div>
)}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,120 @@
import { useState } from "react";
import Button from "../pageElements/Button";
import { useNavigate } from "react-router-dom";
import Col from "react-bootstrap/Col";
import Form from "react-bootstrap/Form";
import Row from "react-bootstrap/Row";
import { removalFields } from "../Constants/Fields/DemoFields";
import { useParams } from "react-router-dom";
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 OptoutPage = () => {
const navigate = useNavigate();
const token = window.sessionStorage.getItem("token");
const [isBusy, setIsBusy] = useState();
const fieldsChunkSize = 2;
const inputFields = removalFields;
const [data, setData] = useState(getFormDataDefaults(inputFields));
const handleChangeInput = (e, name) => {
const newData = handleFormDataChange(e, name, data, inputFields);
if (newData !== null) {
setData(newData);
}
};
const validateData = () => {
const newData = getValidatedFormData(data, inputFields);
const hasErrors = isFormDataHasErrors(newData);
setData(newData);
return hasErrors ? null : objectMap(({ value }) => value, newData);
};
async function saveRequestData(dataValues) {
const requestDate = new Date();
dataValues["requestDate"] = requestDate;
const requestId = uuidv4();
try {
const reqRemoveRef = collection(db, "removalrequests");
await setDoc(doc(reqRemoveRef, requestId), dataValues);
} catch (error) {
console.log(`Error saving request data to db: ${error}`);
}
}
async function handleRequestOptOut() {
const dataValues = validateData();
if (dataValues === null) {
return;
}
console.log("datavalues", dataValues);
saveRequestData(dataValues);
navigate("/");
}
return (
<div className="signup-super-container">
<div className="signup-sub-container">
<div className="signup-header">
<h2 className="signup-header-text">Opt Out of Future Emails</h2>
<p className="requestdemo-para">
Enter your email address to be removed from our mailing list
</p>
</div>
<Form className="demo-request-form">
{splitEvery(Object.keys(inputFields), fieldsChunkSize).map(
(names, j) => (
<Row key={`row${j}`} className="demo-page-row">
{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
? inputFields[name].label
: ""
}
type={inputFields[name].type}
values={inputFields[name].values}
disabled={isBusy}
/>
</Col>
))}
</Row>
)
)}
</Form>
<div className="demo-button-box">
<Button
className="primary-button demo"
type="button"
size="lg"
onClick={handleRequestOptOut}
disabled={isBusy}
labelText={"Request Removal"}
/>
</div>
</div>
</div>
);
};
export default OptoutPage;

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,11 @@
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,827 @@
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 { useParams } from "react-router-dom";
import { db, auth } from "../../firebase";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { useNavigate } from "react-router-dom";
import Toggle from "../../pageElements/Toggle";
import TextInput from "../../pageElements/TextInput";
import { v4 as uuidv4 } from "uuid";
import { collection, setDoc, doc } from "firebase/firestore";
import LoadingSpinner from "../../pageElements/LoadingSpinner";
import { splitEvery } from "../../Utils/Array";
import { ArrowLeftSquare, ArrowRightSquare } from "react-bootstrap-icons";
import {
getFormDataDefaults,
getValidatedFormData,
handleFormDataChange,
isFormDataHasErrors,
} from "../../Utils/Form";
import { objectMap } from "../../Utils/Object";
import {
signupFields,
stateDataValues,
} from "../../Constants/Fields/SignupFields";
import { paymentfields } from "../../Constants/Fields/PaymentFields";
import { signupRadioFields } from "../../Constants/Fields/RadioFields";
import PaymentModal from "../Modals/PaymentModal";
import Radio from "../../pageElements/Radio";
import "../../styles/signup.scss";
import Stripe from "stripe";
import { stripeApiKey, marchEmailPrm } from "../../secrets";
import ConfirmModal from "../Modals/ConfirmModal";
const SignupPage = () => {
const { isUpgrade, currentPlan } = useParams();
const navigate = useNavigate();
const [notice, setNotice] = useState("");
const fieldsChunkSize = 2;
const [isBusy, setIsBusy] = useState(false);
const [data, setData] = useState(getFormDataDefaults(signupFields));
const [paymentData, setPaymentData] = useState(
getFormDataDefaults(paymentfields)
);
const [showPaymentModal, setShowPaymentModal] = useState(false);
const [showSelectPlan, setShowSelectPlan] = useState(
isUpgrade ? true : false
);
//const [showSelectPlan, setShowSelectPlan] = useState(true);
const [activeRadioOption, setActiveRadioOption] = useState("partner");
const [selectedPlan, setSelectedPlan] = useState([signupRadioFields[2]]);
const stripe = new Stripe(stripeApiKey);
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
const [showAddAccount, setShowAddAccount] = useState(false);
const [numberOfAccountsToAdd, setNumberOfAccountsToAdd] = useState();
const [showPromoModal, setShowPromoModal] = useState(false);
const [issueText, setIssueText] = useState("");
const [isAnnual, setIsAnnual] = useState(1);
const [valX, setValX] = useState(1);
const [valY, setValY] = useState(4);
const userAgent = window.navigator.userAgent;
const handleChangeInput = (e, name) => {
const newData = handleFormDataChange(e, name, data, signupFields);
if (newData !== null) {
setData(newData);
}
};
const handleConfirmPromoCode = (code) => {
if (code === marchEmailPrm) {
handlePromoSignup(code);
}
};
const handleChangeAccountSelect = (e, name) => {
const num = e.target.value;
const temp = Number(num);
setNumberOfAccountsToAdd(temp);
};
const handleChangePaymentInput = (e, name) => {
const newPaymentData = handleFormDataChange(
e,
name,
paymentData,
paymentfields
);
if (newPaymentData !== null) {
setPaymentData(newPaymentData);
}
};
const handleChangeRadioInput = (value) => {
const tempPlan = signupRadioFields.filter((plan) => {
if (plan.value === value) {
return plan;
}
});
setSelectedPlan(tempPlan);
setActiveRadioOption(tempPlan[0].value);
};
console.log("selectedPlan in signup page", selectedPlan);
const handleLeftArrowClick = () => {
if (valX == 0) {
return;
} else {
const tempx = valX - 1;
const tempy = valY - 1;
setValX(tempx);
setValY(tempy);
}
};
const handleRightArrowClick = () => {
if (valX == 1) {
return;
} else {
const tempx = valX + 1;
const tempy = valY + 1;
setValX(tempx);
setValY(tempy);
}
};
const vals = [
[1, 2, 3, 4, 5, 6, 7, 8],
[1, 2],
];
const selectAdditionalAccountValues =
selectedPlan[0].value === "partner" ? vals[1] : vals[0];
const validateUserData = () => {
const newData = getValidatedFormData(data, signupFields);
const hasErrors = isFormDataHasErrors(newData);
setData(newData);
return hasErrors ? null : objectMap(({ value }) => value, newData);
};
const validatePaymentData = () => {
const newPaymentData = getValidatedFormData(paymentData, paymentfields);
const hasErrors = isFormDataHasErrors(newPaymentData);
setPaymentData(newPaymentData);
return hasErrors ? null : objectMap(({ value }) => value, newPaymentData);
};
async function saveLeadData(dataValues) {
const signupId = uuidv4();
const {
firstName,
lastName,
firm,
telephone,
streetAddress,
city,
state,
zipCode,
barNumber,
practiceArea,
email,
} = dataValues;
const userData = {
firstName,
lastName,
firm,
telephone,
streetAddress,
city,
state,
zipCode,
barNumber,
practiceArea,
email,
signupId,
};
try {
const collecRef = collection(db, "signupLeads");
await setDoc(doc(collecRef, `${signupId}`), userData);
} catch (error) {
console.log(`Error saving new user to db: ${error}`);
}
}
async function saveUserData(
authId,
dataValues,
customerId,
subscriptionCreated,
subscriptionPeriodStart,
subscriptionPeriodEnd,
subscriptionId,
isPromotionalMebership
) {
const appUserId = uuidv4();
const firmId = uuidv4();
const fbAuthUid = authId;
let plan = selectedPlan;
let docsAllowedPerMonth;
if (isPromotionalMebership === true) {
docsAllowedPerMonth = 1;
subscriptionId = uuidv4();
plan = "promo plan";
} else {
docsAllowedPerMonth = selectedPlan[0].docsAllowedPerMonth;
}
const docsGenerated = 0;
const {
firstName,
lastName,
firm,
telephone,
streetAddress,
city,
state,
zipCode,
barNumber,
practiceArea,
email,
} = dataValues;
const userState = stateDataValues.filter((value) => {
if (value.name === state) return value;
});
const userData = {
docsAllowedPerMonth,
docsGenerated,
appUserId,
fbAuthUid,
firmId,
firstName,
lastName,
firm,
telephone,
streetAddress,
city,
state: userState,
zipCode,
barNumber,
practiceArea,
email,
isPromotionalMebership,
customerId: customerId,
subscriptionId: subscriptionId,
subscriptionPlan: plan,
subscriptionCreated: subscriptionCreated,
subscriptionPeriodStart: subscriptionPeriodStart,
subscriptionPeriodEnd: subscriptionPeriodEnd,
};
try {
const usersRef = collection(db, "users");
const res = await setDoc(doc(usersRef, fbAuthUid), userData);
return res;
} catch (error) {
console.log(`Error saving new user to db: ${error}`);
}
}
const handleProceedToPayment = (e) => {
e.preventDefault();
setShowPaymentModal(true);
};
const handleAdd = () => {
setShowAddAccount(!showAddAccount);
setShowPaymentModal(true);
};
const scrollToLowerPanel = () => {
let target = document.getElementById("scroll-target-id");
if (target) {
window.scrollTo({
top: target.offsetTop,
behavior: "smooth",
});
}
};
const handleCancelModal = () => {
setShowPromoModal(!showPromoModal);
setShowSelectPlan(true);
setTimeout(scrollToLowerPanel, 93);
};
const handleOpenSelectPlan = (e) => {
e.preventDefault();
if (isBusy) {
return;
}
const dataValues = validateUserData();
if (dataValues === null) {
return;
}
saveLeadData(dataValues);
setShowPromoModal(!showPromoModal);
setShowSelectPlan(true);
};
const handleToggle = (val) => setIsAnnual(val);
const handleAddAccounts = () => {
setShowAddAccount(!showAddAccount);
};
const handleCancelAdd = () => {
setShowAddAccount(!showAddAccount);
};
// ******************** STRIPE PAYMENT API CALL ******************** //
async function handleStripeAuthorization(
paymentDataValues,
customerDataValues
) {
const additionalAccounts = numberOfAccountsToAdd
? numberOfAccountsToAdd
: 0;
let error = false;
let subscriptionId = null;
let customerId = null;
const token = await stripe.tokens.create({
card: {
number: paymentDataValues.cardNumber,
exp_month: paymentDataValues.cardExpirationMonth,
exp_year: paymentDataValues.cardExpirationYear,
cvc: paymentDataValues.cardCvvCode,
name:
paymentDataValues.cardFirstName +
" " +
paymentDataValues.cardLastName,
},
});
const type = selectedPlan[0].value ? selectedPlan[0].value : null;
const tempAnnual =
type === "ofCounsel" ? false : isAnnual === 1 ? true : false;
try {
let response;
let data;
if (type === "ofCounsel") {
response = await fetch(`${apiUrl}/new-payment-intent`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": userAgent,
},
body: JSON.stringify({
planType: type,
additionalAccounts: 0,
isAnnual: false,
customerData: {
email: customerDataValues.email,
name: `${customerDataValues.firstName} ${customerDataValues.lastName}`,
},
token,
}),
});
data = await response.json();
} else {
response = await fetch(`${apiUrl}/create-subscription`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
planType: type,
additionalAccounts: additionalAccounts,
isAnnual: tempAnnual,
customerData: {
email: customerDataValues.email,
name: `${customerDataValues.firstName} ${customerDataValues.lastName}`,
},
token,
}),
});
data = await response.json();
}
return {
data,
};
} catch (error) {
console.log(error);
return error;
}
}
// ******************** END STRIPE PAYMENT API CALL ******************** //
const handleSignup = async (e) => {
const paymentDataValues = validatePaymentData();
if (paymentDataValues === null) {
return;
}
let dataValues = validateUserData();
if (dataValues === null) {
return;
}
console.log("dataValues", dataValues);
//const planType = determinePlan(isAnnual, activeRadioOption);
if (paymentDataValues && dataValues) {
setShowPaymentModal(false);
window.scrollTo({ top: 0, behavior: "smooth" });
setIsBusy(true);
try {
const data = await handleStripeAuthorization(
paymentDataValues,
dataValues
);
const customerId = data?.data?.customerId;
const subscriptionCreated = data?.data?.subscriptionCreated;
const subscriptionPeriodStart = data?.data?.subscriptionPeriodStart;
const subscriptionPeriodEnd = data?.data?.subscriptionPeriodEnd;
const subscriptionId = data?.data?.subscriptionId;
if (data === undefined || customerId === undefined) {
setIsBusy(false);
setNotice("Sorry, something went wrong. Please try again.");
return;
} else {
const userCredential = await createUserWithEmailAndPassword(
auth,
dataValues?.email,
dataValues?.password
);
const user = userCredential?.user;
dataValues = { ...dataValues, ...data };
const isPromotionalMebership = false;
const res = await saveUserData(
user?.uid,
dataValues,
customerId,
subscriptionCreated,
subscriptionPeriodStart,
subscriptionPeriodEnd,
subscriptionId,
isPromotionalMebership
);
setIsBusy(false);
navigate("/dashboard");
}
} catch (error) {
console.log("Error handling request", error);
setIsBusy(false);
setNotice("Sorry, something went wrong. Please try again.");
}
}
};
const handlePromoSignup = async (code) => {
let dataValues = validateUserData();
if (dataValues === null) {
return;
}
if (dataValues) {
window.scrollTo({ top: 0, behavior: "smooth" });
setIsBusy(true);
try {
const customerId = `${uuidv4()}-promo`;
const today = new Date();
const subscriptionCreated = today;
const subscriptionPeriodStart = today;
const subscriptionPeriodEnd = "it ends whatever"; //today.setDate(today.getDate() + 30);
const subscriptionId = uuidv4();
const userCredential = await createUserWithEmailAndPassword(
auth,
dataValues.email,
dataValues.password
);
const user = userCredential.user;
dataValues = { ...dataValues };
const isPromotionalMebership = true;
const res = await saveUserData(
user.uid,
dataValues,
customerId,
subscriptionCreated,
subscriptionPeriodStart,
subscriptionPeriodEnd,
subscriptionId,
isPromotionalMebership
);
setIsBusy(false);
navigate("/dashboard");
} catch (error) {
console.log("Error handling request", error, error.message);
setIsBusy(false);
setNotice("Sorry, something went wrong. Please try again.");
}
}
};
const showNoticeOnPage = "" !== notice && !showPaymentModal;
const addAccountCopy =
selectedPlan[0]?.value === "seniorPartner"
? "Add up to eight"
: "Add up to two";
const showAddAccountBtn =
selectedPlan[0].value === "seniorPartner" ||
selectedPlan[0].value === "partner";
const signupForm = (
<div className="signup-sub-container">
{showNoticeOnPage ? (
<>
<br></br>
<div className="alert alert-danger" role="alert">
{notice}
</div>
</>
) : (
<></>
)}
<div className="signup-header">
<h1 className="signup-header-text">Create A Novodraft Account</h1>
{!isUpgrade ? (
<h5 className="signup-header-text trial">
Create an account to start a free trial - save time and draft
smarter
</h5>
) : (
<></>
)}
</div>
<Form className="signup-form">
{splitEvery(
Object.keys(signupFields).slice(0, -2),
fieldsChunkSize
).map((names, j) => (
<Row key={`row${j}`} className="signup-form-row">
{names.map((name, i) => (
<Col
key={`${name}${i + fieldsChunkSize * j}`}
className="mb-3 signup-form-column"
>
<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}
inputClassName="mobile-text-input"
/>
</Col>
))}
</Row>
))}
<Row className="signup-password-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 className="signup-password-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 signup-proceed"
type="button"
size="lg"
onClick={handleOpenSelectPlan}
disabled={isBusy}
labelText="Select A Plan"
/>
</div>
</div>
);
const disableIndex =
currentPlan === "associate" ? 1 : currentPlan === "partner" ? 2 : 0;
return (
<div className="signup-super-container">
{isBusy ? <LoadingSpinner message={""} loaderType="MoonLoader" /> : null}
{!isUpgrade && !isBusy ? signupForm : <></>}
{showSelectPlan && !isBusy ? (
<div className="select-plan-container" id="select-plan-scid">
<div className="select-plan-header">
<div className="plan-header-right">
<h5 className="signup-subheader-text">
Select a subscription plan
</h5>
<p className="subscribe-header-sub">
Click left/right for more options
</p>
</div>
<div className="plan-header-left">
<div className="select-toggle-box">
<p className="select-billing-text">Billed annually</p>
<Toggle value={isAnnual} onClick={handleToggle} />
<p className="select-billing-text">Billed montly</p>
</div>
</div>
</div>
<div className="radio-group-super">
<div className="toggle-arrow-box">
<ArrowLeftSquare
className="arrow-left-toggle"
color="#f7c697"
size="30px"
onClick={handleLeftArrowClick}
style={{ marginTop: "-25px" }}
/>
</div>
<div className="radio-group-box">
{signupRadioFields?.slice(valX, valY).map((option, index) => (
<div className="signup-radio-container">
<Radio
onClick={handleChangeRadioInput}
value={option.value}
name={option.name}
description={option.description}
details={option.details}
activeRadioOption={activeRadioOption}
price={option.pricing}
isAnnual={isAnnual}
isUpgrade={index < disableIndex ? true : false}
disabled={index < disableIndex ? true : false}
currentPlan={currentPlan}
parentIndex={index}
/>
</div>
))}
</div>
<div className="toggle-arrow-box">
<ArrowRightSquare
className="arrow-left-toggle"
color="#f7c697"
size="30px"
onClick={handleRightArrowClick}
style={{ marginTop: "-25px" }}
/>
</div>
</div>
{showAddAccount && !isBusy ? (
<div className="show-addaccount-wrapper">
<div className="show-addaccount-container">
<div className="plan-header-right-lower">
<h5 className="signup-button-wrapper-text">
Add additional accounts
</h5>
<p className="signup-accounts-info">
User profiles will be setup later
</p>
</div>
<div className="signup-select-wrapper">
<div className="signup-select-box">
<select
id="accountNo"
className={`form-select`}
value={numberOfAccountsToAdd}
name={"addAccountsSelect"}
onChange={(e) =>
handleChangeAccountSelect(e, "addAccountsSelect")
}
>
<option
id="ddlProducts"
className={`select-option`}
value=""
></option>
{selectAdditionalAccountValues.map((item, i) => (
<option
id="ddlProducts"
className={`select-option`}
key={`item${i}`}
>
{item}
</option>
))}
</select>
</div>
</div>
<div className="details-foo-box ">
<Button
className="secondary-button"
onClick={handleCancelAdd}
labelText="Cancel"
/>
<div className="upload-button-box">
<Button
className="p-2 mr-2 primary-button signup-proceed-btn"
onClick={handleAdd}
labelText="Proceed To Payment"
/>
</div>
</div>
</div>
</div>
) : (
<></>
)}
<div className="signup-button-box">
{!showAddAccount && !isBusy ? (
<>
{showAddAccountBtn ? (
<Button
className="secondary-button add-account-button"
type="button"
size="lg"
onClick={handleAddAccounts}
disabled={isBusy}
labelText="Add Account(s)"
style={{ marginTight: "12px;" }}
/>
) : (
<></>
)}
<Button
className="primary-button signup-proceed"
type="button"
size="lg"
onClick={handleProceedToPayment}
disabled={isBusy}
labelText="Proceed to Payment"
/>
</>
) : (
<></>
)}
</div>
</div>
) : (
<></>
)}
{showPaymentModal ? (
<PaymentModal
key="modal-one-xc"
paymentData={paymentData}
paymentfields={paymentfields}
setShowPaymentModal={setShowPaymentModal}
handleChangePaymentInput={handleChangePaymentInput}
handleSignup={handleSignup}
notice={notice}
setNotice={setNotice}
selectedPlan={selectedPlan}
isAnnual={isAnnual === 1 ? true : false}
numberOfAccountsToAdd={numberOfAccountsToAdd}
fieldsChunkSize={2}
/>
) : (
<></>
)}
{showPromoModal ? (
<ConfirmModal
titleText="Enter Promo Code"
onCancel={handleCancelModal}
buttonLabelText="Enter Code"
isPromoModal={true}
issueText={issueText}
setIssueText={setIssueText}
onConfirm={handleConfirmPromoCode}
cancelButtonText="I don't have a code"
/>
) : (
<></>
)}
</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;

View File

@@ -0,0 +1,109 @@
export const docEditCopy = {
NewYork: {
prelimaryStatement: function (
documentType,
respondent,
servingParty,
fetchedCase
) {
const position = fetchedCase ? fetchedCase.clientPosition : <></>;
const docCopy =
documentType && documentType === "interrogatories"
? "Interrogatories"
: documentType && documentType === "admissions"
? "Request for Admissions"
: documentType && documentType === "production"
? "Request for Production"
: documentType && documentType.includes("combined")
? "Interrogatories and Request for Production"
: null;
const copyString = `COMES NOW, Respondent(s), ${respondent}, through counsel, in response to the ${docCopy}
served by ${servingParty}, and states as follows:`;
return (
<div className="prelim-statement-container">
<p className="docedit-prelim-text">{copyString}</p>
</div>
);
},
caseNumber: "Index No.",
versusText: "- against -",
},
NewJersey: {
prelimaryStatement: function (documentType, respondent, servingParty) {
const docCopy =
documentType && documentType === "interrogatories"
? "Interrogatories"
: documentType && documentType === "admissions"
? "Request for Admissions"
: documentType && documentType === "production"
? "Request for Production"
: documentType && documentType.includes("combined")
? "Interrogatories and Request for Production"
: null;
const copyString = `COMES NOW, Respondent(s), ${respondent}, through counsel, in response to the ${docCopy}
served by ${servingParty}, and states as follows:`;
return (
<div className="prelim-statement-container">
<p>{copyString}</p>
</div>
);
},
caseNumber: "Docket No.",
versusText: "vs.",
},
Michigan: {
prelimaryStatement: function (documentType, respondent, servingParty) {
const docCopy =
documentType && documentType === "interrogatories"
? "Interrogatories"
: documentType && documentType === "admissions"
? "Request for Admissions"
: documentType && documentType === "production"
? "Request for Production"
: documentType && documentType.includes("combined")
? "Interrogatories and Request for Production"
: null;
const copyString = `COMES NOW, Respondent(s), ${respondent}, through counsel, in response to the ${docCopy}
served by ${servingParty}, and states as follows:`;
return (
<div className="prelim-statement-container">
<p>{copyString}</p>
</div>
);
},
caseNumber: "Case No.",
versusText: "v.",
},
};
export const outgoingComesNow = (state, fetchedCase) => {
let copyString;
const servingParty =
fetchedCase?.clientPosition === "Plaintiff"
? fetchedCase?.plaintiffParties
: fetchedCase?.defendantParties;
const respondingParty =
servingParty === fetchedCase?.plaintiffParties
? fetchedCase?.defendantParties
: fetchedCase?.plaintiffParties;
if (state === "ny") {
copyString = `COMES NOW, ${fetchedCase?.clientPosition}, ${servingParty}, through counsel, and hereby propounds these Interrogatories and Requests for Production on ${respondingParty}, to be answered under oath, in writing, in accordance with NY CPLR 3120 - 3130.`;
} else if (state === "fl") {
copyString = `COMES NOW, ${fetchedCase?.clientPosition}, ${servingParty}, through counsel, and hereby propounds these Interrogatories and Requests for Production on ${respondingParty}, to be answered to be answered under oath, in writing, in accordance with Rule 1.340, Florida Rules of Civil Procedure.`;
} else if (state === "nj") {
copyString =
`Comes now ${fetchedCase?.clientPosition}, ${servingParty}, through counsel, and hereby propounds these Interrogatories and Requests for Production on ${respondingParty}, to be answered to be answered under oath, in writing, in accordance with NJ R. 4:17-1 - 4:18-1,` +
<div className="jersey-itals">et. seq.</div> +
`All questions must be answered unless the court otherwise orders or unless a claim of privilege or protective order is made in accordance with R. 4:17-1(b)(3).`;
}
return <div className="req-edit-comesnow">{copyString}</div>;
};

View File

@@ -0,0 +1,47 @@
export const FeaturesTop = () => {
return (
<div className="features-copy-wrapper">
<h4 className="features-header-text">Discovery responses in minutes</h4>
<ul className="features-list">
<li className="features-list-item">
Don't blink: go from a discovery request on your desk to a precise
response, ready for service, in less time than it takes to order
lunch.
</li>
<li className="features-list-item">
Configurable: adjust the verbosity of your AI assistant's responses.
Dial down for a concise response or up for more analysis and
persuasive arguments.
</li>
<li className="features-list-item">
Perfection, simply: Impressive response documents are editable to your
taste, pre-formatted and comport with applicable local style.
</li>
</ul>
<p className="steps-p"></p>
</div>
);
};
export const FeaturesBottom = () => {
return (
<div className="features-copy-wrapper">
<h4 className="features-header-text">
Discovery requests dramatically fast
</h4>
<ul className="features-list">
<li className="features-list-item-lower">
Straightforward: Easy to use interface enables generation of discovery
requests with a few clicks.
</li>
<li className="features-list-item-lower">
Complete coverage: Outgoing discovery options include interrogatories
and requests for production with instructions and definitions.
</li>
<li className="features-list-item-lower">
Done: finsihed in minutes, with a formatted .docx for service.
</li>
</ul>
</div>
);
};

View File

@@ -0,0 +1,105 @@
import { ExclamationTriangle } from "react-bootstrap-icons";
import "../../styles/howTo-page.scss";
export const Steps = () => (
<div className="steps-wrapper">
<div className="steps-text-wrapper">
<h2 className="doc-header-text">Generate Discovery Responses</h2>
</div>
<div className="fooze">
<div className="steps-upper-left">
<div className="steps-text-wrapper">
<span className="bold-span">CREATE THE CASE: </span> Click Cases in
the top navigation bar to go to the Case View. Click Create New Case
(lower right button) to surface the Create Case Modal. Follow prompts
to enter relevant information. You will end with the Case's Details
View. Click “Upload Document”.
</div>
<div className="steps-text-wrapper">
<span className="bold-span">UPLOAD YOUR DOCUMENT: </span>You will now
see the Document Upload Modal. Enter the discovery request's title,
i.e. Defendants Interrogatories, and (optionally) the date served.
Drag and drop, or click to select. {" "} Then Click{" "}
<span className="bold-span"> Upload.</span>
<div className="mini-break"></div>
<div className="mini-break">
Parsing the document for language processing usually takes fewer
than 2 minutes, but be patient - it may take up to 5.
</div>
</div>
</div>
<div className="steps-upper-right">
<div className="image-right-one">
<div className="image-jhon"></div>
<div className="image-moun">
<img
src="https://imagedelivery.net/OvFc3s8IYmBw7-_bogeTLg/9a7dea0e-db93-4028-ade2-0eda32a5c100/public"
className="howto-hand-img"
></img>
</div>
</div>
</div>
</div>
<div className="barzee">
<div className="steps-text-wrapper">
<span className="bold-span">GENERATE THE RESPONSE: </span>From the
Document List View, click the Generate Responses'' button. Novdrafts
Intellidraft AI will generate the response and automatically navigate to
the interactive Document Editor. Novos: This feature controls the depth
of factual inferences AI will make in articulating arguments; modulated
through a CRAC-based CoT prompt sequence. It is distinct from the
so-called LLM 'temperature' parameter.
</div>
<div className="steps-text-wrapper">
<span className="bold-span">DOWNLOAD YOUR RESPONSE DOCUMENT: </span>
Click the Create .Docx' button to download your fully-formatted,
ready-to-serve response.
</div>
<div className="steps-text-wrapper">
<ExclamationTriangle
style={{ color: "orange", marginRight: "6px", marginBottom: "4px" }}
/>
<span className="bold-span">Pro tip:</span> Once you've created the
case, skip step one - search for the case by entering the case number in
the search bar on your Dashboard, click "Express Document Upload" and
follow the prompts.
</div>
</div>
</div>
);
export const OutgoingSteps = () => (
<div className="steps-wrapper">
<div className="steps-text-wrapper">
<h2 className="doc-header-text">Generate Discovery Requests</h2>
</div>
<div className="barzee">
<div className="steps-text-wrapper">
If you have not yet done so, follow the steps described above in "CREATE
THE CASE". From the Case Details view, click the "Upload Document"
button to surface the document upload modal.
</div>
<div className="steps-text-wrapper">
<span className="bold-span">UPLOAD THE COMPLAINT: </span> Drag and drop,
or click and select the Complaint .pdf from your files. Be sure to
select "Complaint" in the document type section (see image right).
</div>
<div className="steps-text-wrapper">
<span className="bold-span">GENERATE THE RESPONSE: </span>Click
"Upload." Novdrafts Intellidraft AI will generate the response. When
complete, you will be taken to the Document Editor where you may edit
and download the response in .docx format.
</div>
<div className="steps-text-wrapper">
Novodraft currently supports generation of combined interrogatories and
requests for production. Additional request types (i.e. admissions),
features and request generation configurations are launching each month.
</div>
<div className="steps-text-wrapper">
<span className="bold-span">DOWNLOAD YOUR RESPONSE DOCUMENT: </span>
Click the Create .Docx' button to download your fully-formatted,
ready-to-serve response.
</div>
</div>
</div>
);

View File

@@ -0,0 +1,180 @@
export const rogInstructions = [
{
id: "c42293a4-77fd-4c1f-926d-72c36af01298",
text: "These requests are continuing in nature and require you to file supplementary or amended responses if you obtain further, different or contradictory information at any time prior to a trial of this action.",
},
{
id: "17822ae2-d8ae-4d76-aff6-2c0740d7ba0e",
text: "Unless otherwise stated, these requests refer to the time, place, and circumstances of the occurrences described and/or complained of in the Complaint and which form the basis for this action.",
},
{
id: "959f3415-92b1-41f3-8954-b69f186d3988",
text: "Where name and identity of a person is required, state the full name, home address and business address, if known.",
},
{
id: "5ac2d7bb-183d-4e88-91cf-f3fe11810d73",
text: "When the identity of a corporation or other business entity is requested, state the entity's principal place of business, address and telephone number. Also, set forth the individual or individuals with knowledge of the facts and circumstances that form the basis for this action.",
},
{
id: "530e79d2-fa37-4ac0-97de-66a4fa5db5a9",
text: "Where knowledge or information in possession of a party is requested, such request includes knowledge of the party's agents, representatives, and, unless privileged, attorneys. ",
},
{
id: "031da6a5-808b-4cd2-aaaa-f4d72d1e549c",
text: "Where the respondent is a corporation or other business entity, state the name, address and title of persons supplying the information and making the affidavit, and state with particularity the source of his or her information and basis of knowledge.",
},
{
id: "15f855ea-2a7b-4f3d-878b-9c0749ca8e44",
text: "If you are unable to provide an accurate answer to an interrogatory, or are uncertain regarding a response to any item, please provide your best estimate of the information requested, specifying that the answer is merely an estimate, and that an accurate response cannot be given by you to the item.",
},
{
id: "695ec39a-078a-4c43-b9ba-93403a14a0bb",
text: "If you withhold any information or documents on the basis of privilege, confidentiality, relevance, or for any other reason whatsoever please identify it, providing sufficient information to identify the withheld documents or items for this party and the Court to determine whether the documents, items or information should be disclosed.",
},
];
export const rogDefinitions = [
{
text: '"And" and "or" have both conjunctive and disjunctive meanings as necessary to bring within the scope of each request all documents and information that might otherwise be construed to be outside its scope."',
},
{
id: "545bff8d-66de-47fd-9f01-26c945ba8db3",
text: "'You' refers to the party to whom the requests are addressed and any additional parties or entities as described in the foregoing instructions and these definitions.",
},
{
id: "f218a1f6-4d21-4b53-b1c2-69e2d60b95bb",
text: "“Person”, unless otherwise specified”, includes the plural as well as the singular, and includes in its meaning any natural person, or artificial or legal entity, including corporations, partnerships, joint ventures, associations, governmental agencies, groups, organizations, and any and every other form of entity cognizable at law.",
},
{
id: "dda1376c-3d42-471f-9cb8-4a1ecb9c10cc",
text: "“Document” means and includes all written and graphic matter of every kind and description, whether printed or produced by any mechanical, digital or other process, or by hand, whether final draft or reproduction, whether in the actual or constructive possession, custody or control of the respondent to a given interrogatory. This includes any and all written letters, correspondence, memoranda, notes, statements, transcripts, files, charters, articles of incorporation, securities, bonds, stocks, certificates of deposit, evidences of debt, contracts, agreements, licenses, memoranda or notes of telephone or personal conversations, work papers, tapes, charts, reports, books, ledgers, telegrams, sound recordings, books of account, customer account statements, financial statements, catalogs, checks, check stubs, and written statements of witnesses or other persons having knowledge pertaining to the pertinent facts requested or relating to the interrogatory or subpart thereof, whether or not these documents are claimed to be privileged against disclosure.",
},
{
id: "cc62a598-e50e-4e60-ae3a-9163dd73f8a5",
text: '“Describe" means a complete statement of all facts known to you about an incident, event, physical object, person, or other subject matter, together with any expert opinions about the subject matter, the basis for those opinions, and a description of all relevant documents and evidence that supports and/or refutes the fact or opinion.',
},
{
id: "05970162-1b13-4f98-ac73-91da0c8f0bf4",
text: "“Identify” means, when used with reference to an individual person, organization, corporation or association, to state the full name, home and work addresses, e-mail address, home and business telephone numbers, present or last known position and business affiliation and position both in the past and at the time said interrogatory or subpart thereof is being responded to.",
},
{
id: "f4bd002b-08b7-429c-9b1c-c068de17d22c",
text: "The term “identify” further means, when used with reference to a document, to state the date it was created, its author, the signatory, if different, the addressee, the recipient of all copies, the type of document (e.g. chart, memorandum, letter, or other written document) and its present or last known custodian.",
},
{
id: "",
text: '"Refer to" or "refers to" means mentioning, discussing, making reference to or relating to in any way.',
},
{
id: "8ff2c80e-7dd8-4c48-b202-8702047c4946",
text: '"Relate(s) to" or "relating to" means constituting, defining, concerning, embodying, reflecting, identifying, stating, referring to, bearing upon, dealing with or in any way pertaining to.',
},
];
export const foundationRogs = [
{
id: "81094bfb-07d4-4b46-bc73-641ab62ce80b",
text: "State your full name, home addresses for the past ten years, your employer for the last 10 years, your current work address, date of birth and social security number.",
},
{
id: "fb50e08b-2009-4588-89b8-4842481f434a",
text: "Identify all insurance carriers or self-insured funds, by name, address, policy numbers and policy limits for any insurance policy or fund which may provide coverage for any judgment entered against you in this action.",
},
{
id: "72680c6f-4d7e-4e6b-a932-477e0392db98",
text: "If you contend that any other entity or person, including any other party or the Plaintiff, was responsible for the occurrence and Plaintiff's damages, identify such person(s) or entities, and give a concise statement of the facts upon which you rely in support of your contention.",
},
{
id: "6933ce23-8c88-4c6b-877c-c59629685ea1",
text: "Identify all experts who will be called to testify at a trial on the merits of this action, the subject matter and substance of such testimony, a summary of the grounds for each opinion; and produce any written report made by the expert concerning those findings and opinions.",
},
{
id: "ec035502-b3bc-436f-b517-b77721a97b58",
text: " Identify any documents and recordings including, but not limited to, pictures, photographs, PowerPoint presentations for use at trial, demonstrative exhibits, computer generated exhibits, electronically stored data, visual aids, overlays, employment records, plats, visual recorded images, audio recordings, cassette tapes, transcripts of testimony, diagrams and objects relative to the occurrence, the scene of the occurrence, Plaintiffs physical condition or statements made by any party or witness. Identify the substance of the item, the date obtained, what is depicted within the item, and the name and address of the present custodian of each item.",
},
{
id: "ae5e13e3-1296-4d89-a873-8949be9d7bcf",
text: "If you, your insurance carrier, private investigator, or any other person or entity is in possession of any written, oral or recorded statements by any party or person with personal knowledge relative to the occurrence, indicate the date and time each statement was obtained, the name and address of each person who provided the statement, the contents of the statement, and the name and address of the current custodian of the statement.",
},
{
id: "e2c7bb8d-48cf-47e9-b27d-e872dcffdd3d",
text: "Describe any conduct, comment, conversation, statement, or report made by any person at the scene of the occurrence or at any time, concerning fault for the occurrence or facts relevant to any issue in this case. Include in your answer where the conduct, comment, conversation, or statement took place, and in whose presence it was made/observed, as well as the name of the author of such statement, the present custodian of the statement and the address for the custodian.",
},
{
id: "4c3ffbf0-6259-4f43-8996-bdb25e6203a7",
text: "Since your eighteenth birthday, when you were represented by an attorney or waived the right to be represented by an attorney, state whether you have been found guilty of, or plead guilty to, any crimes other than minor traffic violations (i.e., those traffic offenses without the potential penalty of incarceration) and, if so, state the nature of the offense, the date of each conviction, and the full name of the court where each conviction was entered.",
},
{
id: "4c3ffbf0-6259-4f43-8996-bdb25e6203a7",
text: "If you are aware of any other case or proceeding involving the occurance identified in the Complaint including, but not limited to, civil, criminal or administrative actions, identify the case or action by tribunal, case number, docket number or citation number, the date of any hearing, and indicate any pleas in the case(s) and the disposition of the matter(s).",
},
];
export const standardProd = [
{
id: "e0863692-c5da-4516-b85c-233a7f811a34",
text: "All documents identified, directly or indirectly, in your answers to Interrogatories.",
index: 0,
},
{
id: "0d648f06-b6f9-4264-bcf2-c3111fe3f976",
text: "All written reports of all expert witnesses with whom you or your attorneys have consulted, including but not limited to those you expect to and/or have identified as persons you will call as expert witnesses at any trial of this action.",
index: 1,
},
{
id: "8c8573de-1a5c-4828-ba99-d51f610136a5",
text: "All documents upon which any expert witness you intend to call at trial relied upon in formulating an opinion about the subject matter of this action.",
index: 2,
},
{
id: "63ab0999-c877-4ebc-9094-6d343294411d",
text: "The most recent curriculum vitae of each expert whom you expect to call as an expert witness at trial.",
index: 3,
},
{
id: "5b3ac105-c026-4f37-b335-2ab83adb6658",
text: "All notes, correspondence, bills, invoices, diagrams, photographs, or other documents prepared or reviewed by each person whom you expect to call as an expert witness at trial.",
index: 4,
},
{
id: "eb382a63-1c0b-4a78-9bba-d67380bb6367",
text: "All invoices generated by expert witnesses for performing expert witness services to you, including but not limited to fees for medical examination, records review, pretrial preparation, any telephone conference, any trial testimony anticipated and any other fee paid to experts.",
index: 5,
},
{
id: "a07bbc03-2f14-4837-965b-67ee261f25e4",
text: "Any and all written, recorded, or signed statements of any party, including the plaintiff, defendant, witnesses, investigators, or agent, representative or employee of the parties concerning the subject matter of this action.",
index: 6,
},
{
id: "495b094d-36f2-4a83-971e-54b1b5312515",
text: "All documents, photographs, videotapes or audio tapes, digital recordings or recordings in any medium, diagnostic images, diagrams, records, surveys or other graphic representations of information concerning the subject matter/occurrences that form the basis of this action.",
index: 7,
},
{
id: "cdcc44cf-13b2-4011-adb5-ed435156a0f8",
text: "Any documents which afforded, described or pertained to liability insurance coverage for the occurrences which are the subject matter of the complaint.",
index: 8,
},
{
id: "7b85bf1d-ce31-4cb5-baac-b678f3afb418",
text: "Any documents identified or described in any other parties' answers to Interrogatories that are in your possession, custody or control.",
index: 9,
},
{
id: "5b2033f9-2d14-49d4-848a-8c3630bb78d8",
text: "Any documents received pursuant to a subpoena duces tecum or a request under the Freedom of Information Act in this case.",
index: 10,
},
{
id: "fe9ce1d8-265f-4ebc-9856-99715cbb23ee",
text: "Any document prepared during the regular course of business as a result of the occurrence complained of in the complaint.",
index: 11,
},
{
id: "0012041e-8618-4864-b084-9c354261731a",
text: "Copies of any treaties, standards in the industry, legal authority, rule, case, statute, or code that will be relied upon in the prosecution of this action.",
index: 12,
},
];

View File

@@ -0,0 +1,426 @@
export const createCaseFields = {
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 },
venue: { required: true, maxLength: 45 },
trialDate: { required: true, maxLength: 45, type: "date" },
filedDate: { required: true, maxLength: 45, type: "date" },
lawFirm: { required: false, maxLength: 45 },
};
export const createCaseAdditionalFields = {
contactEmail: { required: true, maxLength: 45, type: "email" },
contactName: { required: true, maxLength: 45 },
clientPosition: { required: true, default: "Plaintiff" },
caseType: { required: true, maxLength: 45 },
billingCode: { required: true, maxLength: 45 },
leadAttorneys: { required: true, maxLength: 45 },
};
export const floridaCourts = [
{
circuit: "First",
counties: ["Escambia", "Okaloosa", "Santa Rosa", "Walton"],
},
{
circuit: "Second",
counties: [
"Franklin",
"Gadsden",
"Jefferson",
"Leon",
"Liberty",
"Wakulla",
],
},
{
circuit: "Third",
counties: [
"Columbia",
"Dixie",
"Hamilton",
"Lafayette",
"Madison",
"Suwannee",
"Taylor",
],
},
{
circuit: "Fourth",
counties: ["Clay", "Duval", "Nassau"],
},
{
circuit: "Fifth",
counties: ["Citrus", "Hernando", "Lake", "Marion", "Sumter"],
},
{
circuit: "Sixth",
counties: ["Pasco", "Pinellas"],
},
{
circuit: "Seventh",
counties: ["Flagler", "Putnam", "St. Johns", "Volusia"],
},
{
circuit: "Eigth",
counties: ["Alachua", "Baker", "Bradford", "Gilchrist", "Levy", "Union"],
},
{
circuit: "Ninth",
counties: ["Orange", "Osceola"],
},
{
circuit: "Tenth",
counties: ["Hardee", "Highlands", "Polk"],
},
{
circuit: "Eleventh",
counties: ["Miami-Dade"],
},
{
circuit: "Twelfth",
counties: ["DeSoto", "Manatee", "Sarasota"],
},
{
circuit: "Thirteenth",
counties: ["Hillsborough"],
},
{
circuit: "Fourteenth",
counties: ["Bay", "Calhoun", "Gulf", "Holmes", "Jackson", "Washington"],
},
{
circuit: "Fifteenth",
counties: ["Palm Beach"],
},
{
circuit: "Sixteenth",
counties: ["Monroe"],
},
{
circuit: "Seventeenth",
counties: ["Broward"],
},
{
circuit: "Eighteenth",
counties: ["Brevard", "Seminole"],
},
{
circuit: "Nineteenth",
counties: ["Indian River", "Martin", "Okeechobee", "St. Lucie"],
},
{
circuit: "Twentieth",
counties: ["Charlotte", "Collier", "Glades", "Hendry", "Lee"],
},
];
export const michiganCourts = [
{
circuit: "1st",
counties: ["Hillsdale County"],
},
{
circuit: "2nd",
counties: ["Berrien County"],
},
{
circuit: "3rd",
counties: ["Wayne County"],
},
{
circuit: "4th",
counties: ["Jackson County"],
},
{
circuit: "5th",
counties: ["Barry County"],
},
{
circuit: "6th",
counties: ["Oakland County"],
},
{
circuit: "7th",
counties: ["Genesee County"],
},
{
circuit: "8th",
counties: ["Ionia County", "Montcalm County"],
},
{
circuit: "9th",
counties: ["Kalamazoo County"],
},
{
circuit: "10th",
counties: ["Saginaw County"],
},
{
circuit: "11th",
counties: [
"Alger County",
"Luce County",
"Mackinac County",
"Schoolcraft County",
],
},
{
circuit: "12th",
counties: ["Baraga County", "Houghton County", "Keweenaw County"],
},
{
circuit: "13th",
counties: ["Antrim County", "Leelanau County", "Grand Traverse County"],
},
{
circuit: "14th",
counties: ["Muskegon County"],
},
{
circuit: "15th",
counties: ["Branch County"],
},
{
circuit: "16th",
counties: ["Macomb County"],
},
{
circuit: "17th",
counties: ["Kent County"],
},
{
circuit: "18th",
counties: ["Bay County"],
},
{
circuit: "19th",
counties: ["Benzie County", "Manistee County"],
},
{
circuit: "20th",
counties: ["Ottawa County"],
},
{
circuit: "21st",
counties: ["Isabella County"],
},
{
circuit: "22nd",
counties: ["Washtenaw County"],
},
{
circuit: "23rd",
counties: [
"Alcona County",
"Arenac County",
"Iosco County",
"Oscoda County",
],
},
{
circuit: "24th",
counties: ["Sanilac County"],
},
{
circuit: "25th",
counties: ["Marquette County"],
},
{
circuit: "26th",
counties: ["Alpena County", "Montmorency County"],
},
{
circuit: "27th",
counties: ["Lake County", "Newaygo County"],
},
{
circuit: "28th",
counties: ["Missaukee County", "Wexford County"],
},
{
circuit: "29th",
counties: ["Clinton County", "Gratiot County"],
},
{
circuit: "30th",
counties: ["Ingham County"],
},
{
circuit: "31st",
counties: ["St. Clair County"],
},
{
circuit: "32nd",
counties: ["Gogebic County", "Ontonagon County"],
},
{
circuit: "33rd",
counties: ["Charlevoix County"],
},
{
circuit: "34th",
counties: ["Ogemaw County", "Roscommon County"],
},
{
circuit: "35th",
counties: ["Shiawasee County"],
},
{
circuit: "36th",
counties: ["Van Buren County"],
},
{
circuit: "37th",
counties: ["Calhoum County"],
},
{
circuit: "38th",
counties: ["Monroe County"],
},
{
circuit: "39th",
counties: ["Lenawee County"],
},
{
circuit: "40th",
counties: ["Lapeer County"],
},
{
circuit: "41st",
counties: ["Dickinson County", "Iron County", "Menominee County"],
},
{
circuit: "42nd",
counties: ["Midland County"],
},
{
circuit: "43rd",
counties: ["Cass County"],
},
{
circuit: "44th",
counties: ["Livingston County"],
},
{
circuit: "45th",
counties: ["St. Joseph County"],
},
{
circuit: "45th",
counties: ["St. Joseph County"],
},
{
circuit: "46th",
counties: ["Crawford County", "Kalkaska County", "Otsego County"],
},
{
circuit: "47th",
counties: ["Delta County"],
},
{
circuit: "49th",
counties: ["Mecosta County", "Osceoloa County"],
},
{
circuit: "51st",
counties: ["Mason County", "Oceana County"],
},
{
circuit: "52nd",
counties: ["Huron County"],
},
{
circuit: "53rd",
counties: ["Cheboygan County", "Presque Isle County"],
},
{
circuit: "55th",
counties: ["Tuscola County"],
},
{
circuit: "55th",
counties: ["Clare County", "Gladwin County"],
},
{
circuit: "56th",
counties: ["Eaton County"],
},
{
circuit: "57th",
counties: ["Emmet County"],
},
{
circuit: "50th",
counties: ["Chippewa County"],
},
];
export const newYorkCounties = [
{ name: "Albany County" },
{ name: "Allegany County" },
{ name: "Bronx County" },
{ name: "Broome County" },
{ name: "Cattaraugus County" },
{ name: "Cayuga County" },
{ name: "Chautauqua County" },
{ name: "Chemung County" },
{ name: "Chenango County" },
{ name: "Clinton County" },
{ name: "Columbia County" },
{ name: "Cortland County" },
{ name: "Delaware County" },
{ name: "Dutchess County" },
{ name: "Erie County" },
{ name: "Essex County" },
{ name: "Franklin County" },
{ name: "Fulton County" },
{ name: "Genesee County" },
{ name: "Greene County" },
{ name: "Hamilton County" },
{ name: "Herkimer County" },
{ name: "Jefferson County" },
{ name: "Kings County" },
{ name: "Lewis County" },
{ name: "Livingston County" },
{ name: "Madison County" },
{ name: "Monroe County" },
{ name: "Montgomery County" },
{ name: "Nassau County" },
{ name: "New York County " },
{ name: "Niagara County" },
{ name: "Oneida County" },
{ name: "Onondaga County" },
{ name: "Ontario County" },
{ anme: "Orange County" },
{ name: "Orleans County" },
{ name: "Oswego County" },
{ name: "Otsego County" },
{ name: "Putnam County" },
{ name: "Queens County" },
{ name: "Rensselaer County" },
{ name: "Richmond County" },
{ name: "Rockland County" },
{ name: "Saint Lawrence County" },
{ name: "Saratoga County" },
{ name: "Schenectady County" },
{ name: "Schoharie County" },
{ name: "Schuyler County" },
{ name: "Seneca County" },
{ name: "Steuben County" },
{ name: "Suffolk County" },
{ name: "Sullivan County" },
{ name: "Tioga County" },
{ name: "Tompkins County" },
{ name: "Ulster County" },
{ name: "Warren County" },
{ name: "Washington County" },
{ name: "Wayne County" },
{ name: "Westchester County" },
{ name: "Wyoming County" },
{ name: "Yates County" },
];

View File

@@ -0,0 +1,40 @@
export const demoFields = {
firstName: { required: true, label: "First Name", maxLength: 45 },
lastName: { required: true, label: "Last Name", maxLength: 45 },
telephone: {
required: true,
label: "Telephone: (###) ###-####",
maxLength: 45,
type: "phone",
},
email: { required: true, label: "Email", maxLength: 45, type: "email" },
firm: { required: true, label: "Firm", minLength: 3, maxLength: 45 },
firmSize: {
required: true,
label: "Firm Size (num of attorneys)",
minLength: 3,
maxLength: 45,
},
};
export const supportFields = {
firstName: { required: true, label: "First Name", maxLength: 45 },
lastName: { required: true, label: "Last Name", maxLength: 45 },
telephone: {
required: true,
label: "Telephone: (###) ###-####",
maxLength: 45,
type: "phone",
},
email: { required: true, label: "Email", maxLength: 45, type: "email" },
supportIssue: {
required: true,
label: "Support Issue",
minLength: 3,
maxLength: 45,
},
};
export const removalFields = {
email: { required: true, label: "Email", maxLength: 45, type: "email" },
};

View File

@@ -0,0 +1,38 @@
export const paymentfields = {
cardFirstName: {
required: true,
label: "Card First Name",
maxLength: 45,
type: "cardFirstName",
},
cardLastName: {
required: true,
label: "Card Last Name",
type: "cardLastName",
},
cardNumber: {
required: true,
label: "Card Number",
minLength: 16,
maxLength: 16,
type: "cardNumber",
},
cardCvvCode: {
required: true,
label: "CVV Number",
maxLength: 5,
type: "cardCvvCode",
},
cardExpirationMonth: {
required: true,
label: "Month",
maxLength: 2,
type: "cardExpirationMonth",
},
cardExpirationYear: {
required: true,
label: "Year",
maxLength: 4,
type: "cardExpirationYear",
},
};

View File

@@ -0,0 +1,99 @@
export const signupRadioFields = [
{
name: "Of Counsel",
label: "Of Counsel",
value: "ofCounsel",
description: [
"Single document/one-time fee",
"Intellidraft AI - downloadable document in minutes",
"Documents formatted for jurisdiction",
"24/7 support",
],
details: "",
docsAllowedPerMonth: 1,
pricing: [
{ type: "one-time", amount: 59 },
{ type: "annDiscount", amount: 59 },
{ type: "additionalAccount", amount: 0 },
],
},
{
name: "Associate",
label: "Associate",
value: "associate",
description: [
"1 document per month",
"Intellidraft AI - service-ready document in minutes",
"Carryover 1 unused document each subscription period",
"Documents formatted for jurisdiction",
"24/7 support",
"First month is free!",
],
details: "",
docsAllowedPerMonth: 1,
pricing: [
{ type: "monthly", amount: 53 },
{ type: "annDiscount", amount: 38 },
{ type: "additionalAccount", amount: 0 },
],
},
{
name: "Partner",
label: "Partner",
value: "partner",
description: [
"3 documents per month",
"Intellidraft AI - service-ready document in minutes",
"Carryover 1 unused document per month",
"Documents formatted for jurisdiction",
"24/7 support",
"Up to 2 additional accounts: $49/each",
],
details: "",
docsAllowedPerMonth: 3,
pricing: [
{ type: "monthly", amount: 71 },
{ type: "annDiscount", amount: 56 },
{ type: "additionalAccount", amount: 49 },
],
},
{
name: "Senior Partner",
label: "Senior Partner",
value: "seniorPartner",
description: [
"Unlimited documents.",
"Intellidraft AI - service-ready document in minutes",
"Documents formatted for jurisdiction",
"24/7 Triage-level support response",
"Up to 5 additional accounts at $39/each",
],
details: "",
docsAllowedPerMonth: 50000,
pricing: [
{ type: "monthly", amount: 132 },
{ type: "annDiscount", amount: 119 },
{ type: "additionalAccount", amount: 39 },
],
},
];
/*
{
name: "Of Counsel",
label: "Of Counsel",
value: "ofCounsel",
description: [
"One document - pay as you go",
"24/7 support",
"Document conforming with state rules",
],
details: "",
docsAllowedPerMonth: 1,
pricing: [
{ type: "oneTime", amount: 59 },
{ type: "oneTime", amount: 59 },
{ type: "oneTime", amount: 59 },
],
},
*/

View File

@@ -0,0 +1,63 @@
export const signupFields = {
firstName: { required: true, label: "First Name", maxLength: 45 },
lastName: { required: true, label: "Last Name", maxLength: 45 },
firm: { required: true, label: "Firm", minLength: 3, maxLength: 45 },
telephone: {
required: true,
label: "Telephone: (###) ###-####",
maxLength: 45,
type: "phone",
},
streetAddress: {
required: true,
label: "Street Address",
minLength: 3,
maxLength: 45,
},
city: { required: true, label: "City", maxLength: 45 },
state: {
required: true,
label: "State",
minLength: 3,
maxLength: 45,
type: "select",
values: ["Florida", "Michigan", "New Jersey", "New York"],
},
zipCode: {
required: true,
label: "Zip Code (5 Digit)",
maxLength: 6,
type: "zip",
},
barNumber: { required: true, label: "Bar Number", maxLength: 45 },
practiceArea: { required: true, label: "Practice Area", maxLength: 45 },
email: {
required: true,
label: "Email",
maxLength: 45,
type: "email",
inputWidth: "80%",
},
password: {
required: true,
label: "Password",
maxLength: 45,
minLength: 8,
type: "password",
},
confirmPassword: {
required: true,
label: "Confirm Password",
maxLength: 45,
minLength: 8,
compareTo: "password",
type: "confirmPassword",
},
};
export const stateDataValues = [
{ name: "Florida", value: "florida", code: "fl" },
{ name: "Michigan", value: "michigan", code: "mi" },
{ name: "New York", value: "newYork", code: "ny" },
{ name: "New Jersey", value: "newJersey", code: "nj" },
];

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
import { useNavigate } from "react-router-dom";
import { userStateListener } from "../firebase";
import { createContext, useState, useEffect } from "react";
export const AuthContext = createContext({
currentUserEmail: undefined,
currentUserCreatedAt: undefined
});
export const AuthProvider = ({ children, history }) => {
const [currentUser, setCurrentUser] = useState({
currentUserEmail: undefined,
currentUserCreatedAt: undefined
});
const navigate = useNavigate();
useEffect(() => {
const unsubscribe = userStateListener((user) => {
const token = user?.accessToken;
if (token) {
window.sessionStorage.setItem("token", token);
} else {
window.sessionStorage.removeItem("token");
}
if (!user) {
setCurrentUser({
currentUserEmail: null,
currentUserCreatedAt: undefined
});
} else {
let createdAt = new Date(parseInt(user.metadata.createdAt, 10));
if (isNaN(createdAt)) {
createdAt = undefined;
}
setCurrentUser({
currentUserEmail: user.email,
currentUserCreatedAt: createdAt
});
}
});
return unsubscribe;
}, [navigate]);
return <AuthContext.Provider value={currentUser}>{children}</AuthContext.Provider>;
};

View File

@@ -0,0 +1,54 @@
import React, { createContext, useContext, useEffect, useReducer } from "react";
import { useGroupReducer } from "../useReducer/reducers";
import { AuthContext } from "../../Context/AuthProvider";
import { db } from "../../firebase.js";
import {
collection,
getDocs,
query,
where,
onSnapshot,
} from "firebase/firestore";
const initialState = {
group: undefined,
};
const AppContext = createContext({
state: initialState,
dispatch: () => null,
});
const AppProvider = ({ children }) => {
const { currentUserEmail } = useContext(AuthContext);
const [appState, appDispatch] = useReducer(useGroupReducer, initialState);
useEffect(() => {
if (typeof currentUserEmail === "undefined") {
return;
}
if (!currentUserEmail) {
appDispatch({ type: "DELETE_GROUP" });
return;
}
return onSnapshot(
query(collection(db, "users"), where("email", "==", currentUserEmail)),
(snapshot) => {
const doc = snapshot.docs[0];
if (typeof doc === "undefined") {
appDispatch({ type: "DELETE_GROUP" });
} else {
appDispatch({ type: "CREATE_GROUP", payload: doc.data() });
}
}
);
}, [currentUserEmail]);
return (
<AppContext.Provider value={{ appState, appDispatch }}>
{children}
</AppContext.Provider>
);
};
export { AppContext, AppProvider };

View File

@@ -0,0 +1,22 @@
import React, { createContext, useReducer } from "react";
import { useDataReducer } from "../useReducer/reducers";
const initialState = {
cases: [{}],
};
const DataContext = createContext({
state: initialState,
dispatch: () => null,
});
const DataProvider = ({ children }) => {
const [dataState, dataDispatch] = useReducer(useDataReducer, initialState);
return (
<DataContext.Provider value={{ dataState, dataDispatch }}>
{children}
</DataContext.Provider>
);
};
export { DataContext, DataProvider };

View File

@@ -0,0 +1,15 @@
import { useQuery } from "react-query";
import { db } from "../../firebase";
import { collection } from "firebase/firestore";
const casesRef = collection(db, "cases");
export const useCases = (userId) => {
return useQuery(userId, async () => {
const queryParams = {
userId: userId,
};
const response = "someEndpoint";
//return response
});
};

View File

@@ -0,0 +1,23 @@
export const useGroupReducer = (state, action) => {
switch (action.type) {
case "CREATE_GROUP":
return { ...state, group: action.payload };
case "UPDATE_GROUP_NAME":
return [...state, { name: action.payload }];
case "DELETE_GROUP":
return { ...state, group: null };
default:
return state;
}
};
export const useDataReducer = (state, action) => {
switch (action.type) {
case "CREATE_CASES":
return { ...state, cases: action.payload };
case "UPDATE_GROUP_NAME":
return [...state, { cases: action.payload }];
default:
return state;
}
};

View File

@@ -0,0 +1,11 @@
export const splitEvery = (inputArray, perChunk) => {
const chunks = inputArray.reduce((resultArray, item, index) => {
const chunkIndex = Math.floor(index / perChunk)
if (!resultArray[chunkIndex]) {
resultArray[chunkIndex] = [];
}
resultArray[chunkIndex].push(item);
return resultArray;
}, []);
return chunks;
};

108
ax3Client/src/Utils/Form.js Normal file
View File

@@ -0,0 +1,108 @@
import { objectMap } from "./Object";
import {
isValidDateString,
isValidEmailString,
isValidTelephoneString,
isValidZipCodeString,
} from "./String";
export const getFormDataDefaults = (fields, defaults) =>
objectMap(
(field, key) => ({
value:
(typeof defaults === "undefined" ? field.default : defaults[key]) || "",
error: false,
message: "",
}),
fields
);
export const getValidatedFormData = (data, fields) =>
objectMap((state, key) => {
const field = fields[key];
const value = state.value.trim();
let error = false;
let message = "";
if (!error && field.required && value === "") {
error = true;
message = "This field is required";
}
if (
!error &&
typeof field.minLength !== "undefined" &&
((!field.required && value === "") || value.length < field.minLength)
) {
error = true;
message = `Must be at least ${field.minLength} characters`;
}
if (
!error &&
field.type === "date" &&
((!field.required && value === "") || !isValidDateString(value))
) {
error = true;
message = "Date format must be MM/DD/YYYY";
}
if (
!error &&
field.type === "email" &&
((!field.required && value === "") || !isValidEmailString(value))
) {
error = true;
message = "Invalid email format";
}
if (
!error &&
field.type === "phone" &&
((!field.required && value === "") || !isValidTelephoneString(value))
) {
error = true;
message = "Telephone format must be (###) ###-####";
}
if (
!error &&
field.type === "zip" &&
((!field.required && value === "") || !isValidZipCodeString(value))
) {
error = true;
message = "Zip Code must be 5 Digit";
}
if (
!error &&
field.type === "confirmPassword" &&
data[field.compareTo].value !== value
) {
error = true;
message = "Passwords do not match";
}
return {
...state,
value,
error,
message,
};
}, data);
export const handleFormDataChange = (e, name, data, fields) => {
if (!fields) {
return;
}
const value =
(typeof e?.target?.value === "undefined" ? e.value : e.target.value) || "";
const field = fields[name];
if (
typeof field.maxLength === "undefined" ||
value.length <= field.maxLength
) {
return { ...data, [name]: { ...data[name], value } };
}
return null;
};
export const isFormDataHasErrors = (data) => {
return Object.values(data).reduce(
(hasErrors, { error }) => hasErrors || error,
false
);
};

View File

@@ -0,0 +1,33 @@
export const determinePlan = (isAnnual, activeRadioOption) => {
if (isAnnual === true && activeRadioOption === "associate") {
return {
planType: "Annual Assoc",
planId: "8a7b889e-8626-4d3e-a37d-ef2a8b762419",
};
} else if (isAnnual === false && activeRadioOption === "associate") {
return {
planType: "Monthly Assoc",
planId: "4e177c87-d074-4409-9f5e-6358adff5017",
};
} else if (isAnnual === true && activeRadioOption === "partner") {
return {
planType: "annualPartner",
planId: "45b567bc-c1fb-47dd-bfb1-c180380141bc",
};
} else if (isAnnual === false && activeRadioOption === "partner") {
return {
planType: "monthlyPartner",
planId: "c70bc329-9c17-4997-a7d3-fe8f62db49b0",
};
} else if (isAnnual === true && activeRadioOption === "seniorPartner") {
return {
planType: "annualSeniorPartner",
planId: "35b3109c-5fe3-46e3-a64c-1f43fe7136c4",
};
} else if (isAnnual === false && activeRadioOption === "seniorPartner") {
return {
planType: "monthlySeniorPartner",
planId: "a88ce618-cee2-42ae-ab4c-e404b2a5422d",
};
}
};

View File

@@ -0,0 +1,7 @@
export const objectMap = (callback, inputObject) => Object.keys(inputObject).reduce(
(acc, key) => {
acc[key] = callback(inputObject[key], key);
return acc
},
{}
);

View File

@@ -0,0 +1,103 @@
export const isValidDateString = (str) => {
const strVal = String(str);
const re = /^\d{2}\/\d{2}\/\d{4}$/;
if (!re.test(strVal)) {
return false;
}
return !isNaN(new Date(strVal));
};
export const isValidEmailString = (str) => {
const re =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(str).toLowerCase());
};
export const isValidZipCodeString = (str) => {
const re = /^\d{5}/;
return re.test(String(str));
};
export const isValidTelephoneString = (str) => {
const re = /^(\+0?1\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/;
return re.test(String(str));
};
export const capFirstChar = (string) => {
return string?.charAt(0).toUpperCase() + string?.slice(1);
};
export const remArtifacts = (string) => {
string.replace("request no.", "");
};
export const capMonths = (string) => {
if (!string) {
return;
}
const months = [
"january",
"february",
"march",
"april",
"may",
"june",
"july",
"august",
"september",
"october",
"november",
"december",
];
let pre = null;
let post = null;
months.forEach((month) => {
if (string?.includes(month)) {
pre = string.substring(0, string?.indexOf(month));
const temp = string.slice(string.indexOf(month));
post = temp?.charAt(0).toUpperCase() + temp?.slice(1);
}
});
if (pre) {
if (post) {
return pre + post;
}
} else {
return string;
}
};
export const remSig = (string) => {
if (!string) {
return;
}
if (!string?.includes("respectfully")) {
return string;
}
const temp = string?.substring(0, string.indexOf("respectfully"));
return temp;
};
export const scrubAll = (string) => {
if (!string) {
return;
}
const all = capFirstChar(remSig(capMonths(string)));
const final = all.replace("first set of admissions", "");
const final2 = final.replace("request", "");
return final2;
};
export const insertAt = (string) => {
if (!string) {
return;
}
const toFind = "RESPONDENT";
const char = "\n";
function findPosition(string, toFind) {
return string?.lastIndexOf(toFind);
}
const pos = findPosition(string, toFind);
return string.slice(0, pos) + char + string.slice(pos);
};

46
ax3Client/src/firebase.js Normal file
View File

@@ -0,0 +1,46 @@
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import {
getAuth,
onAuthStateChanged,
signOut,
signInWithEmailAndPassword,
} from "firebase/auth";
import {
apiKey,
authDomain,
projectId,
storageBucket,
messagingSenderId,
appId,
measurementId,
} from "./secrets";
const firebaseConfig = {
apiKey: apiKey,
authDomain: authDomain,
projectId: projectId,
storageBucket: storageBucket,
messagingSenderId: messagingSenderId,
appId: appId,
measurementId: measurementId,
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
export const signInUser = async (email, password) => {
if (!email && !password) return;
return await signInWithEmailAndPassword(auth, email, password);
};
export const userStateListener = (callback) => {
return onAuthStateChanged(auth, callback);
};
export const SignOutUser = async () => await signOut(auth);
export { auth };
export { db };

22
ax3Client/src/index.js Normal file
View File

@@ -0,0 +1,22 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { AppProvider } from "./Hooks/useContext/appContext";
import { DataProvider } from "./Hooks/useContext/dataContext";
import { AuthProvider } from "./Context/AuthProvider";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import "./styles/index.scss";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<DataProvider>
<BrowserRouter>
<AuthProvider>
<AppProvider>
<App />
</AppProvider>
</AuthProvider>
</BrowserRouter>
</DataProvider>
);

View File

@@ -0,0 +1,19 @@
import React from "react";
const Button = (props) => {
const { className, variant, color, onClick, labelText, disabled } = props;
const buttonClass = disabled ? "primary-disabled" : className;
return (
<button
disabled={disabled}
className={buttonClass}
variant={variant}
color={color}
onClick={onClick}
>
{labelText}
</button>
);
};
export default Button;

View File

@@ -0,0 +1,395 @@
import React from "react";
import Button from "../pageElements/Button.js";
import { FileEarmarkText } from "react-bootstrap-icons";
import TextInput from "../pageElements/TextInput";
import Tooltip from "../pageElements/Tooltip";
import { InfoCircle } from "react-bootstrap-icons";
import { toDate } from "firebase/firestore";
export const InfoCard = (props) => {
const { data, onEdit, onCancel, onSave, onChangeInput, isEditing, isBusy } =
props;
const {
caseNumber,
caption,
jurisdiction,
judge,
venue,
caseType,
trialDate,
filedDate,
contactFirstName,
contactLastName,
contactEmail,
attorneys,
parties,
} = data;
const editingContent = () => {
return (
<>
<div className="info-column">
<div className="info-row">Case caption:</div>
<TextInput
id="caption"
disa
inputClassName="info-edit-input"
value={caption}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "caption")}
/>
<div className="info-row">Jurisdiction:</div>
<TextInput
id="jurisdiction"
inputClassName="info-edit-input"
value={jurisdiction}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "jurisdiction")}
/>
<div className="info-row">Judge:</div>
<TextInput
id="judge"
inputClassName="info-edit-input"
value={judge}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "judge")}
/>
<div className="info-row">Venue:</div>
<TextInput
id="venue"
inputClassName="info-edit-input"
value={venue}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "venue")}
/>
{caseType || isEditing ? (
<>
<div className="info-row">Case type:</div>
<div>
<TextInput
id="caseType"
inputClassName="info-edit-input"
value={caseType}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "caseType")}
/>
</div>
</>
) : (
<></>
)}
<div className="info-row">Trial date:</div>
<TextInput
id="trialDate"
inputClassName="info-edit-input"
value={trialDate}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "trialDate")}
/>
</div>
<div className="info-column">
<div className="info-row">Index number:</div>
<TextInput
id="caseNumber"
inputClassName="info-edit-input"
value={caseNumber}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "caseNumber")}
/>
<div className="info-row">Date filed:</div>
<TextInput
id="dateFiled"
inputClassName="info-edit-input"
value={filedDate}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "filedDate")}
/>
<div className="info-row">
Contact:
<TextInput
id="contactFirst"
inputClassName="info-edit-input"
value={contactFirstName}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "contactFirstName")}
/>
<TextInput
id="contactLast"
inputClassName="info-edit-input"
value={contactLastName}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "contactLastName")}
/>
</div>
<div className="info-row">Contact email:</div>
<TextInput
id="contactEmail"
inputClassName="info-edit-input"
value={contactEmail}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "contactEmail")}
/>
<div className="info-row">Lead attorneys:</div>
<TextInput
id="leadAttorneys"
inputClassName="info-edit-input"
value={attorneys}
disabled={isBusy}
onChange={(e) => onChangeInput(e, "attorneys")}
/>
</div>
</>
);
};
const infoContent = () => {
return (
<>
<div className="info-column">
<div className="info-row">
Case caption: <p className="info-text"> {caption}</p>
</div>
<div className="info-row">
Jurisdiction: <p className="info-text"> {jurisdiction}</p>
</div>
<div className="info-row">
Judge: <p className="info-text"> {judge}</p>
</div>
<div className="info-row">
Venue: <p className="info-text"> {venue}</p>
</div>
{caseType ? (
<div className="info-row">
Case type: <p className="info-text"> {caseType}</p>
</div>
) : (
<></>
)}
<div className="info-row">
Trial date: <p className="info-text"> {trialDate}</p>
</div>
</div>
<div className="info-column">
<div className="info-row">
Index number: <p className="info-text"> {caseNumber}</p>
</div>
<div className="info-row">
Date filed: <p className="info-text"> {filedDate}</p>
</div>
<div className="info-row">
Contact:
<p className="info-text">
{contactFirstName} {contactLastName}
</p>
</div>
<div className="info-row">
Contact email: <p>{contactEmail}</p>
</div>
<div className="info-row">
Lead attorneys: <p className="info-text"> {attorneys}</p>
</div>
</div>
</>
);
};
return (
<div className="info-card-outer">
<div className="info-upper-wrapper">
{isEditing ? editingContent() : infoContent()}
</div>
<div className="info-button-wrapper">
{isEditing ? (
<>
<Button
onClick={() => onSave()}
labelText="Save"
className="btn primary-button save-button"
disabled={isBusy}
/>
<Button
onClick={() => onCancel()}
labelText="Cancel"
className="btn primary-button cancel-button"
disabled={isBusy}
/>
</>
) : (
<Button
onClick={() => onEdit()}
labelText="Edit"
className="btn primary-button edit-button"
disabled={isBusy}
/>
)}
</div>
</div>
);
};
export const CaseCard = (props) => {
const {
caption,
captionTwo,
caseNumber,
filedDate,
jurisdiction,
caseId,
onClick,
labelText,
} = props;
return (
<div className="linkcard-container">
<div className="card-col1">
{caption} v. {captionTwo}
</div>
<div className="card-col2">{jurisdiction}</div>
<div className="card-col3">
<p className="card-text">{`Filed: ${filedDate}`}</p>
</div>
<div className="card-col4">{caseNumber}</div>
<div className="card-viewButton-container">
<Button
onClick={() => onClick(caseId)}
labelText={labelText}
className="btn btn-primary view-button"
/>
</div>
</div>
);
};
export const DocCard = (props) => {
const {
title,
parentCaseNumber,
parentCaseName,
parentCaseId,
documentId,
dateServed,
docType,
displayDeleteButton,
handleNavigate,
responseGenerations,
confirmDelete,
createdAt,
clientPosition,
} = props;
const createdAtTime = createdAt?.seconds
? createdAt?.seconds
: new Date().getTime();
const now = new Date().getTime();
const uploadedAt = new Date(createdAtTime * 1000);
const isReportable = Math.round((now - uploadedAt) / 60 / 1000) > 15;
function convertDate(dateVar) {
const date = new Date(parseInt(dateVar?.seconds) * 1000);
const date2 = date.toLocaleString("en-US", { month: "long" });
const temp = String(date).split(" ");
return `${date2} ${temp[2]}, ${temp[3]}`;
}
const disabled = docType ? false : true;
const tooltipText =
"Parsing is taking a bit longer than usual. This usually resolves within 15 minutes. If it does not, click “report” and support will address the issue within 24 hours.";
const pendingText =
"Parsing in process, please be patient. This should resolve within 10 minutes.";
const report = (documentId, docType) => {
//console.log("report documentId, docType", documentId, docType);
};
const documentTitle =
docType === "interrogatories-out"
? "Outbound request"
: "Discovery response";
const isOutbound = docType === "interrogatories-out";
const buttonText =
docType === "interrogatories-out" ? "Edit Request" : "Edit Response";
return (
<div className="doc-card-container">
<div className="doc-card-input">
<div className="card-col-icon">
<FileEarmarkText className="doc-file-icon" size="22px" />
</div>
<div className="doc-col2">
{documentTitle}{" "}
{disabled && isReportable ? <Tooltip text={tooltipText} /> : <></>}
{disabled && !isReportable ? (
<Tooltip text={pendingText} iconStyle="clock" />
) : (
<></>
)}
</div>
<div className="doc-col4">
<div className="jurisdiction-box">{parentCaseName}</div>
</div>
{createdAt ? (
<div className="doc-col2 responsive-none">
Created: {convertDate(createdAt)}
</div>
) : (
<></>
)}
{disabled && isReportable ? (
<div className="doc-col5">
<Button
labelText="Report"
className="btn btn-primary report-button"
onClick={() => report(documentId, docType)}
/>
</div>
) : (
<div className="doc-col5">{parentCaseNumber}</div>
)}
<div className="doc-col6">
{displayDeleteButton ? (
<Button
onClick={() =>
confirmDelete(documentId, docType, responseGenerations)
}
labelText="Delete"
className="btn btn-primary view-button delete-one"
/>
) : (
<></>
)}
</div>
</div>
<div className="doccard-button-wrapper">
<Button
disabled={disabled}
className={"primary-button pt-2 pb-2 gen-button"}
onClick={() =>
documentId &&
handleNavigate(
documentId,
parentCaseId,
docType,
clientPosition,
responseGenerations
)
}
labelText={buttonText}
/>
</div>
</div>
);
};
/*
{disabled ? (
isReportable ? (
<Tooltip text={tooltipText} />
) : (
<Tooltip text={pendingText} iconStyle="clock" />
)
) : (
<></>
)}
*/

View File

@@ -0,0 +1,80 @@
import React from "react";
import "../styles/detailcard.scss";
export const DetailCard = (props) => {
const { data } = props;
const {
caseNumber,
caption,
jurisdiction,
judge,
venue,
caseTags,
trialDate,
filedDate,
contactName,
contactEmail,
leadAttorney,
parties,
} = data;
return (
<div className="det-card-card-outer">
<div className="detail-card-row">
<div className="detail-card-col">
<div className="detail-card-detail">
<p className="detail-subhead"> Trial Date:</p>
<p className="detail-text"> {trialDate}</p>
</div>
<div className="detail-card-detail">
<p className="detail-subhead"> Date Filed:</p>
<p className="detail-text"> {filedDate}</p>
</div>
<div className="detail-card-detail">
<p className="detail-subhead">Lead attorney(s):</p>
<p className="detail-text">
{" "}
{leadAttorney ? leadAttorney : "Not listed"}
</p>
</div>
</div>
<div className="detail-card-col">
<div className="detail-card-detail">
<p className="detail-subhead">Jurisdiction:</p>
<p className="detail-text"> {jurisdiction}</p>
</div>
<div className="detail-card-detail">
<p className="detail-subhead">Venue:</p>
<p className="detail-text"> {venue}</p>
</div>
<div className="detail-card-detail">
<p className="detail-subhead"> Contact:</p>
<p className="detail-text">{contactName}</p>
</div>
</div>
<div className="detail-card-col">
<div className="detail-card-detail">
<p className="detail-subhead">Judge:</p>
<p className="detail-text"> {judge}</p>
</div>
<div className="detail-card-casetags">
<p className="detail-subhead">Case Type:</p>
<p className="detail-text">
{caseTags.map((tag) => (
<div className="casetag-text">{tag.tagName}</div>
))}
</p>
</div>
<div className="detail-card-detail">
<p className="detail-subhead">Contact email:</p>
<p className="detail-text"> {contactEmail}</p>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,65 @@
import React, { useLayoutEffect, useRef } from "react";
const EditElement = (props) => {
const {
className,
handleEditValue,
value,
setShowInputEle,
showInputEle,
handleFocus,
handleBlur,
i,
} = props;
const MIN_TEXTAREA_HEIGHT = 32;
const textareaRef = useRef(null);
useLayoutEffect(() => {
// Reset height to shrink on delete
textareaRef.current.style.height = "inherit";
// Set height
textareaRef.current.style.height = `${Math.max(
textareaRef.current.scrollHeight,
MIN_TEXTAREA_HEIGHT
)}px`;
}, [value]);
return (
<div>
{showInputEle ? (
<div
style={{ width: "100%" }}
onBlur={(e) => handleBlur(e, i)}
tabIndex="0"
className="unshow-ele"
></div>
) : (
<div
style={{ width: "100%" }}
onClick={() => handleFocus("plain-div onCLick, i", i)}
onBlur={(e) => handleBlur(e, i)}
className="outer-div"
tabIndex="0"
>
<textarea
ref={textareaRef}
name={"text-box"}
type="text"
style={{
minHeight: MIN_TEXTAREA_HEIGHT,
resize: "none",
color: "#4e4e4e",
letterSpacing: ".02rem",
}}
value={value}
className="edit-ele-input"
onChange={(e) => handleEditValue(e, i)}
autoFocus
/>
</div>
)}
</div>
);
};
export default EditElement;

View File

@@ -0,0 +1,24 @@
import Pagination from "react-bootstrap/Pagination";
const ListPagination = (props) => {
const { totalPages, page, setPage } = props;
const handlePaginationItemClick = (event) => {
const targetPage = Number(event.currentTarget.dataset.page);
if (!isNaN(targetPage)) {
setPage(targetPage);
}
};
return (
<Pagination className="justify-content-end">
{page !== 1 ? <Pagination.Prev data-page={page - 1} onClick={handlePaginationItemClick}/> : null}
{Array.from({ length: totalPages }, (v, i) => i + 1).map(n => (
<Pagination.Item key={`page${n}`} active={n === page} data-page={n} onClick={handlePaginationItemClick}>{n}</Pagination.Item>
))}
{page !== totalPages ? <Pagination.Next data-page={page + 1} onClick={handlePaginationItemClick}/> : null}
</Pagination>
);
};
export default ListPagination;

View File

@@ -0,0 +1,55 @@
import { useState } from "react";
import RingLoader from "react-spinners/RingLoader";
import MoonLoader from "react-spinners/MoonLoader";
import "../styles/spinner.scss";
const LoadingSpinner = (props) => {
const { message, loaderType } = props;
let [loading, setLoading] = useState(true);
const color = "#31e0f7";
const override = {
display: "flex",
flexDirection: "column",
margin: "12px auto",
borderColor: "#a4ebf4",
};
const isMoon = loaderType === "MoonLoader" ? true : false;
return (
<div>
{isMoon ? (
<div>
<div className="spinner-outer-wrapper moon">
<MoonLoader
color={color}
loading={loading}
cssOverride={override}
size={150}
aria-label="Loading Spinner"
data-testid="loader"
/>
</div>
</div>
) : (
<div className="spinner-outer-wrapper">
<div className="spinner-container">
<div className="loading-text-box">
<p className="loading-graph">{message}</p>
</div>
<RingLoader
color={color}
loading={loading}
cssOverride={override}
size={150}
aria-label="Loading Spinner"
data-testid="loader"
/>
</div>
</div>
)}
</div>
);
};
export default LoadingSpinner;

View File

@@ -0,0 +1,16 @@
import React from "react";
const ProgressBar = (props) => {
const {
value,
total
} = props;
return (
<div className="progress-bar">
<div className="progress-bar-fill" style={{ width: `${Math.min(Math.ceil(100 * value / total), 100)}%` }} />
</div>
);
};
export default ProgressBar;

View File

@@ -0,0 +1,80 @@
import { CircleFill, Circle } from "react-bootstrap-icons";
import "../styles/radio.scss";
const Radio = (props) => {
const {
name,
description,
onClick,
value,
price,
disabled,
activeRadioOption,
details,
isAnnual,
isUpgrade,
currentPlan,
parentIndex,
} = props;
const classCheckbox = "checkbox";
const classOption = "option";
const index = isAnnual === 1 ? Number("1") : Number("0");
const upgradeClass = isUpgrade ? "upgrade" : "not-upgrade";
return (
<div className="radio-container">
<div className="radio-option-container" key={name}>
<>
<div className={`radio-circle-container ${upgradeClass}`}>
<div
className="radio-circle-top"
onClick={() => !disabled && onClick && onClick(value)}
>
{value == activeRadioOption && !isUpgrade ? (
<div className="radio-circle-box">
<CircleFill color="#1a76c7" size="14px" />
</div>
) : (
<div className="radio-circle-box">
<Circle color="#1a76c7" size="14px" />
</div>
)}
<div className="radio-name-box"> {`${name}`} </div>
<div className="radio-price-box">
{value === "ofCounsel" ? (
<div className="radio-price">
{`$${price[index]["amount"]}/doc`}
</div>
) : (
<div className="radio-price">
{`$${price[index]["amount"]}/mo`}
</div>
)}
</div>
</div>
<div className="radio-circle-lower">
<div className="radio-description-box">
{description.map((item, i) => {
const el =
item == "First month is free!" && !isUpgrade ? (
<div className="offer-div">
<p className="signup-feat-item-bold">{item}</p>
</div>
) : (
<p className="signup-feat-item">{item}</p>
);
return el;
})}
</div>
<div className="radio-description-box">{`${details}`} </div>
</div>
</div>
</>
</div>
</div>
);
};
export default Radio;

View File

@@ -0,0 +1,116 @@
import { CircleFill, Circle, ExclamationTriangle } from "react-bootstrap-icons";
const Radiogroup = (props) => {
const {
className,
classNames,
labelText,
title,
price,
ref,
onClick,
value,
options,
disabled,
radioError,
} = props;
const classCheckbox = "checkbox";
const classOption = "option";
return (
<div className="radio-option-container">
<div className="radio-group-title-box">
{title === "Document type" ? (
<div className="checkbox-title-icon-box">
<ExclamationTriangle
style={{
color: "red",
marginRight: "8px",
marginBottom: "6px",
}}
/>
</div>
) : (
<></>
)}
{title && <div className="checkbox-title">{title}</div>}
</div>
<div className="checkbox-option-div">
{options &&
options?.length &&
options.map((option, index) => (
<div
className="option-container"
key={option.name}
onClick={() => !disabled && onClick && onClick(option)}
>
<div className="checked-icon">
{value === option.value ? (
<div className="zora">
<div className="circle-container">
<CircleFill
className={
value === option.value
? "clickable-icon-active"
: "clieckable-icon"
}
color="#1a76c7"
size="20px"
onClick={(e) => {
!disabled && onClick(e);
}}
/>
</div>
<div
className="option-label"
htmlFor={"helper-radio-" + index}
>
{option.name}
</div>
</div>
) : (
<div className="zora">
<div className="circle-container">
<Circle
className={
value === option.value
? "clickable-icon-active"
: "clieckable-icon"
}
color="#1a76c7"
size="20px"
onClick={(e) => {
!disabled && onClick(e);
}}
/>
</div>
<div
className="option-label"
htmlFor={"helper-radio-" + index}
>
{option.name}
</div>
</div>
)}
</div>
</div>
))}
</div>
<div className="radio-error-box">
{radioError ? (
<div className="modal-document-error">{radioError}</div>
) : (
<></>
)}
</div>
</div>
);
};
/**
* A radio button is a quick way to extract a single coded answer from our users by presenting multiple possibilities and allowing only one option to be chosen.
*/
export default Radiogroup;

View File

@@ -0,0 +1,18 @@
import Dropdown from "react-bootstrap/Dropdown";
import DropdownButton from "react-bootstrap/DropdownButton";
const SelectDropdown = (props) => {
const { titleText, dropDownOptions, handleSelect, className } = props;
return (
<DropdownButton className={className} id="dropdown--button" title={`${titleText}`}>
{dropDownOptions?.map((option, i) => (
<Dropdown.Item key={i} onClick={() => handleSelect(`${option.value}`)}>
{`${option.label}`}
</Dropdown.Item>
))}
</DropdownButton>
);
};
export default SelectDropdown;

View File

@@ -0,0 +1,42 @@
/*
<Typeahead
className="flex-grow-1 create-case-taginput"
id="case-type-search"
labelKey="tagName"
options={parsedTags}
minLength={1}
ref={typeaheadRef}
placeholder="Case Type - start typing"
onChange={(selected) => {
let item = null;
for (const currentItem of selected) {
item = { ...currentItem };
}
setSelectedTag(item);
typeaheadRef.current.clear();
}}
/>
<div className="case-tag-container">
{selectedCaseTags.map((tag, i) => (
<div className="tag-container">
<div className="tag-box">
<div className="tag-ex-box">
<div className="tag-ex"> x</div>
</div>
<div className="casetag-name">{tag}</div>
</div>
</div>
))}
</div>
<Radiogroup
title="Client position"
options={radioOptions}
value={radioValue}
disabled={isBusy}
onClick={(e) => setRadioValue(e, "clientPosition")}
/>
*/

View File

@@ -0,0 +1,80 @@
import React from "react";
import "../styles/text-input.scss";
const TextInput = (props) => {
const {
error,
message,
inputClassName,
labelClassName,
label,
name,
value,
values,
onChange,
id,
state,
type,
disabled,
} = props;
const isObject = state === "fl" || state === "mi";
return (
<div
className={
inputClassName
? `pe-input-container form-floating mb-3 ${inputClassName}`
: "pe-input-container form-floating mb-3"
}
>
{label ? (
<label htmlFor="signup" className="form-label">
{label}
</label>
) : (
<></>
)}
{type === "select" ? (
<select
id={id}
className={`form-select`}
value={value}
name={name}
disabled={disabled}
onChange={onChange}
>
<option
id="input-select-option"
className={`select-option`}
value=""
></option>
{values && values != undefined ? (
values.map((item, i) => (
<option
id="ddlProducts"
className={`select-option`}
key={`item${i}`}
>
{isObject ? `${item.circuit} Judicial Circuit` : item}
</option>
))
) : (
<></>
)}
</select>
) : (
<input
id={id}
className={`form-control ${inputClassName}`}
value={value}
name={name}
disabled={disabled}
onChange={onChange}
type={type}
/>
)}
{error ? <div className="textinput-error-box">{message}</div> : <></>}
</div>
);
};
export default TextInput;

View File

@@ -0,0 +1,31 @@
import ToggleButton from "react-bootstrap/ToggleButton";
import ToggleButtonGroup from "react-bootstrap/ToggleButtonGroup";
const Toggle = (props) => {
const { onClick, handleToggle, value } = props;
return (
<>
<ToggleButtonGroup
type="radio"
name="options"
value={value}
onChange={handleToggle}
defaultValue={2}
>
<ToggleButton
onClick={(e) => onClick(2)}
id="tbg-radio-1"
value={2}
></ToggleButton>
<ToggleButton
onClick={(e) => onClick(1)}
id="tbg-radio-2"
value={1}
></ToggleButton>
</ToggleButtonGroup>
</>
);
};
export default Toggle;

View File

@@ -0,0 +1,25 @@
import { useState } from "react";
import "../styles/tooltip.scss";
import { InfoCircle, Clock } from "react-bootstrap-icons";
function Tooltip(props) {
const [showMessage, setShowMessage] = useState(false);
const { text, iconStyle } = props;
const showClock = iconStyle === "clock";
return (
<div
className="tooltip-container"
onMouseEnter={() => setShowMessage(true)}
onMouseLeave={() => setShowMessage(false)}
>
{showClock ? (
<Clock className="infocircle-icon" size="16px" />
) : (
<InfoCircle className="infocircle-icon" size="16px" />
)}
{showMessage ? <span className="tooltip-text">{text}</span> : <></>}
</div>
);
}
export default Tooltip;

View File

@@ -0,0 +1,209 @@
@use "sass:map";
$account-padding-base: 15px;
$account-border-radius: 6px;
$account-menu-item-padding: 6px;
$account-card-background: #f5f5f5;
$account-menu-item-hover-background: #f5f5f5;
$account-support-title-padding: 10px;
.account-container {
margin-top: 25px;
display: flex;
flex-direction: column;
gap: $account-padding-base;
}
.account-column {
display: flex;
flex-direction: column;
gap: $account-padding-base;
background-color: #fff;
margin-bottom: 2rem;
border: 1px solid #e9e9e9;
border-radius: $account-border-radius;
}
.account-left-column {
display: flex;
flex-direction: column;
gap: $account-padding-base;
background-color: #f5f5f5;
border: 1px solid #e9e9e9;
border-radius: $account-border-radius;
height: 1136px;
}
.account-section {
font-weight: 500;
margin-left: 20px;
}
.account-group {
display: flex;
flex-direction: column;
gap: $account-padding-base;
}
.account-card {
background: $account-card-background;
border-radius: 6px;
margin: 6px 6px;
padding: $account-padding-base;
text-align: center;
&-title {
font-size: 1.1em;
color: $primary;
margin: 0 0 $account-padding-base 0;
}
&-description {
margin: $account-padding-base 0;
}
&-actions {
margin: $account-padding-base 0 0 0;
text-align: center;
}
&-right-account {
border: 1px solid #e9e9e9;
}
}
.account-docs-generated {
border: 1px solid #e9e9e9;
.account-card-title {
margin: $account-padding-base 0;
font-size: 1.3em;
}
.account-card-actions {
text-align: right;
}
&-progress-bar {
margin: $account-padding-base 0;
}
}
.cancel-sub-button {
width: 165px;
}
.account-support {
display: grid;
grid-template-columns: 2fr 1fr;
grid-gap: $account-padding-base;
text-align: left;
&-title {
position: absolute;
top: 0;
padding: 0 6px;
font-size: 0.9em;
background: #a0a0a0;
color: $white;
}
&-info {
position: relative;
padding-top: $account-support-title-padding + $account-padding-base;
&-title {
left: -$account-padding-base;
border-radius: 0px $account-border-radius $account-border-radius 0px;
}
}
&-links {
position: relative;
overflow: hidden;
background: $white;
border-radius: $account-border-radius;
padding: $account-support-title-padding + $account-padding-base
$account-padding-base $account-padding-base $account-padding-base;
&-title {
left: 0;
border-radius: 0px 0px $account-border-radius 0px;
}
}
}
.account-info {
text-align: left;
&-name {
font-weight: bold;
}
}
.account-menu {
padding: $account-padding-base - $account-menu-item-padding;
border-radius: $account-border-radius;
&-item {
margin: 2px 0px;
letter-spacing: 0.03rem;
font-weight: 500;
a,
a:hover,
a:link,
a:visited {
display: block;
color: inherit;
padding: $account-menu-item-padding;
text-decoration: none;
}
a:hover {
background-color: #fff;
border-left: 1px solid var(--accent-orange);
}
}
}
.account-subscriptions {
text-align: left;
&-title {
font-size: 1.1em;
font-weight: 500;
}
&-item {
margin: $account-padding-base 0;
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
&-label {
font-weight: 500;
}
&-value {
flex: 0 0 80%;
max-width: 80%;
}
}
&-actions {
text-align: right;
}
}
@media only screen and (min-width: map.get($grid-breakpoints, "md")) {
.account-container {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
grid-gap: $account-padding-base;
}
}
@media only screen and (min-width: map.get($grid-breakpoints, "lg")) {
.account-group {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: $account-padding-base;
}
}

View File

@@ -0,0 +1,167 @@
:root {
--font-dark: #000;
--font-regular: #5f5f5f;
--font-medium: #767676;
--font-light: #868686;
--font-lighter: #868686;
--gattlin-grey: #a9a9a9;
--ultra-light-accent: #e4e4e4;
--barely-there-accent: #f9f9f9;
--accent-orange: #f27300;
--accent-orange-light: #ffd7b1;
--background-blue: rgb(240, 247, 250);
--bluest-blue: #003858;
--haq-blue: rgb(242, 248, 255);
}
html {
height: 100vh;
}
body {
height: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
.main-container {
display: flex;
flex-direction: column;
min-height: 700px;
margin-top: 15px;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
h1,
h2,
h3,
p,
div,
button {
color: var(--font-regular);
font-family: "Roboto";
}
p {
margin-bottom: 0rem;
}
.app-divider {
border-bottom: 1px solid #d2d2d2;
}
.tos-container {
margin: 20px 10px;
padding: 5px 20px;
}
.tos-para {
margin: 10px 0px;
}
/*** Media Queries **/
@media only screen and (max-width: 420px) {
.main-container {
max-width: 390px;
}
.mobile-content-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 18px 2px 0px 2px;
padding: 2px 2px;
border-radius: 18px;
}
.lower-foo {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: auto;
background-color: var(--background-blue);
border-radius: 15px;
}
.mobile-divider {
height: 8px;
width: 88%;
margin: 2px 0px 16px 0px;
padding: 6px 0px;
border-bottom: 1px solid var(--accent-orange-light);
}
.mobile-info-heading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 28px 6px 2px 6px;
text-align: justify;
font-size: 15px;
line-height: 21px;
}
.mobile-info-heading-dashboard {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 2px 6px 2px 6px;
text-align: justify;
font-size: 15px;
line-height: 21px;
}
.mobile-text-dashboard {
margin-bottom: 6px;
padding: 0px 6px;
}
.notication-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-right: auto;
margin-left: auto;
margin-bottom: 40px;
}
.notication-text-container {
display: flex;
flex-direction: row;
width: 100%;
font-size: 1rem;
margin-top: 12px;
}
.notification-text {
margin-left: 16px;
}
.notication-container {
margin: 20px 0px;
min-height: 100px;
width: 320px;
background-color: var(--barely-there-accent);
border-radius: 15px;
}
.notication-text-container {
display: flex;
flex-direction: row;
width: 100%;
font-size: 1rem;
margin-left: 12px;
}
}

View File

@@ -0,0 +1,63 @@
$button-height: 45px;
$button-padding: 0 5px;
.primary-button {
height: $button-height;
padding: $button-padding;
min-width: 150px;
background-color: #1a76c7;
color: #fff;
border-radius: 8px;
border-style: none;
&:hover {
border: 2px solid #f27300;
}
}
.primary-disabled {
height: $button-height;
padding: $button-padding;
min-width: 156px !important;
background-color: #87beee;
color: #e7e7e7;
border-radius: 8px;
border-style: none;
&:hover {
border: none;
}
}
.upgrade-account {
width: 190px;
}
.secondary-button {
height: $button-height;
padding: $button-padding;
min-width: 150px;
color: #1a76c7;
background-color: #fff;
border-radius: 8px;
border: 1px solid #1a76c7;
&:hover {
background-color: var(--background-blue) !important;
}
}
.add-account-button {
margin-right: 12px;
}
.cancel-button {
height: $button-height;
padding: $button-padding;
min-width: 115px;
color: #1a76c7;
background-color: #fff;
border-radius: 8px;
border: 1px solid #1a76c7;
&:hover {
background-color: var(--background-blue) !important;
color: #1a76c7;
}
}

View File

@@ -0,0 +1,18 @@
.florida-court-header {
text-align: center;
}
.florida-court-header-text {
color: #373737;
font-weight: 500;
}
.florida-court-header-text2 {
color: #373737;
font-weight: 500;
margin-top: -5px;
}
.florida-inner {
font-weight: 400;
}

View File

@@ -0,0 +1,328 @@
.linkcard-container {
display: flex;
flex-direction: row;
border-radius: 6px;
margin: 15px 0px;
height: 65px;
width: 100%;
justify-content: space-between;
border: 1px solid #a9a9a9;
}
.link-card-button1 {
border: none;
border-radius: 4px;
color: #fff;
height: 32px;
width: 68px;
font-weight: 700;
margin-right: 10px;
}
.link-card-button2 {
border: none;
border-radius: 4px;
color: #fff;
height: 32px;
width: 68px;
font-weight: 700;
}
.card-viewButton-container {
display: flex;
flex-direction: row-reverse;
align-items: center;
width: 8%;
height: 100%;
padding-right: 5px;
margin-right: 10px;
padding-top: 1px;
}
.card-col-icon {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 100%;
margin-left: 5px;
padding-left: 5px;
margin-right: 20px;
}
.card-col1 {
display: flex;
align-items: center;
width: 25%;
height: 100%;
margin-left: 5px;
padding-left: 5px;
}
.card-col2 {
display: flex;
align-items: center;
width: 20%;
height: 100%;
padding-left: 5px;
padding-bottom: 2px;
}
.card-col3 {
display: flex;
justify-content: flex-end;
align-items: center;
width: 15%;
height: 100%;
padding-left: 5px;
}
.card-col4 {
display: flex;
justify-content: flex-end;
align-items: center;
width: 15%;
height: 100%;
padding-left: 5px;
}
.card-button-col {
padding-top: 15px;
padding-bottom: 15px;
width: 15%;
display: flex;
margin-right: 15px;
}
.info-card-outer {
display: flex;
flex-direction: column;
background-color: #f4f4f4;
border: 1px solid #cdcdcd;
padding: 10px 10px;
border-radius: 10px;
width: 680px;
}
.info-upper-wrapper {
display: flex;
flex-direction: row;
padding: 8px 20px;
border-radius: 10px;
}
.info-column {
display: flex;
flex-direction: column;
width: 50%;
margin-right: 5px;
}
.button-box {
height: 200px;
}
.view-button {
width: 80px;
height: 35px;
}
.report-button {
background-color: #fff !important;
border: 1px solid var(--accent-orange) !important;
color: var(--accent-orange) !important;
width: 80px;
height: 35px;
}
.report-button:hover {
background-color: rgb(253, 245, 231) !important;
}
.card-text1 {
margin-top: 17px;
margin-right: 20px;
padding-top: 4px;
}
.jurisdiction-box {
min-width: 130px;
}
.doc-card-container {
display: flex;
flex-direction: row;
margin: 6px 0px;
height: 60px;
width: 100%;
}
.doc-card-input {
display: flex;
flex-direction: row;
align-items: center;
width: 95%;
background-color: #fff;
border-radius: 6px;
border: 1px solid #a9a9a9;
}
.doc-col2 {
display: flex;
align-items: center;
width: 200px;
height: 100%;
padding-top: 1px;
margin-right: 15px;
}
.doc-col3 {
display: flex;
align-items: center;
min-width: 190px;
height: 100%;
padding-top: 1px;
margin-right: 15px;
}
.doc-col4 {
display: flex;
flex: 1;
align-items: center;
min-width: 220px;
height: 100%;
padding-top: 1px;
margin-right: 15px;
}
.doc-col5 {
display: flex;
justify-content: flex-end;
align-items: center;
width: 120px;
height: 100%;
padding-right: 10px;
margin-right: 15px;
}
.doc-col6 {
width: 90px;
margin-right: 15px;
}
.doc-er {
display: flex;
flex-direction: row-reverse;
align-items: center;
width: 20%;
margin-left: -8px;
}
.info-row {
display: flex;
flex-direction: column;
color: #000;
margin-left: 4px;
font-weight: 400;
letter-spacing: 1px;
padding: 5px 5px;
}
.button-container {
display: flex;
flex-direction: row-reverse;
align-items: center;
margin-right: 25px;
margin-bottom: 3px;
}
.edit-button {
height: 32px;
width: 80px;
margin-right: 10px;
padding-top: 3px !important;
border: 1px solid #1a76c7 !important;
background-color: #fff;
color: #1a76c7;
}
.edit-button:hover {
background-color: #f2fbfd;
color: #1a76c7;
}
.save-button {
height: 32px;
width: 80px;
margin-right: 10px;
padding-top: 3px !important;
background-color: #1a76c7;
color: #fff;
&:hover {
background-color: #1a76c7;
color: #ffff;
}
}
.cancel-button {
height: 32px;
width: 80px;
padding-top: 3px !important;
margin-right: 20px;
border: 1px solid #1a76c7 !important;
background-color: #fff;
color: #1a76c7;
}
.doc-file-icon {
//cursor: pointer;
}
.cards-item-actions {
display: flex;
gap: 1rem;
}
.info-text {
margin-top: 10px;
}
.delete-one {
padding-top: 5px !important;
}
.info-edit-input {
padding: 0px 0px 0px 10px !important;
height: 40px !important;
min-height: 0px !important;
width: 90% !important;
margin-top: 10px;
}
.doccard-button-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-left: 10px;
width: 158px;
}
.delete-doc-button {
min-width: 80px !important;
width: 80px !important;
&:hover {
border: none !important;
border-color: #1a76c7 !important;
}
}
.gen-button {
width: 158px !important;
}
@media only screen and (max-width: 1080px) {
.responsive-none {
display: none;
}
}

View File

@@ -0,0 +1,181 @@
.details-container {
display: flex;
flex-direction: column;
margin-right: auto;
margin-left: auto;
align-items: center;
}
.details-heading-container {
display: flex;
flex-direction: column;
width: 100%;
margin-top: 20px;
margin-bottom: 20px;
padding: 10px;
border: 1px solid var(--ultra-light-accent);
border-radius: 10px;
background-color: var(--barely-there-accent);
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
}
.details-subhead {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 8px;
}
.pheno-phun {
display: flex;
flex-direction: row;
}
.details-col {
display: flex;
flex-direction: column;
justify-content: center;
margin: auto;
}
.details-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin: auto;
}
.details-button-container {
display: flex;
flex-direction: column;
width: 100%;
margin: 12px 0px;
}
.details-button-box {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 8px;
margin-right: 1px;
}
.details-complaint-button-box {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 8px;
margin-right: 1px;
}
.details-complaint-button-box > .back-button {
&:hover {
border: 2px solid #f27300;
}
}
.back-button-box {
margin-right: 8px;
}
.details-heading {
font-weight: 500;
margin-bottom: 0.5rem;
margin-right: 8px;
}
.details-heading2 {
margin-left: 2px;
margin-right: 12px;
margin-bottom: 12px !important;
font-size: 20px;
font-weight: 500;
color: var(--font-medium);
}
.details-heading3 {
margin-left: 26px;
padding-left: 12px;
margin-bottom: 12px !important;
font-size: 20px;
font-weight: 500;
color: var(--font-medium);
}
.details-heading4 {
padding-left: 12px;
margin-bottom: 12px !important;
margin-right: 8px;
font-size: 20px;
font-weight: 500;
color: var(--font-medium);
}
.details-butn-wrapper {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding-left: 16px;
}
.details-butn-wrapper > .primary-button {
margin-right: 5px;
}
.upload-button-box {
margin-left: 8px;
}
.details-upload-button-box {
display: flex;
justify-content: flex-end;
width: 260px;
margin-left: 8px;
}
.details-upload-button-box > .primary-button {
width: 250px;
}
.details-upload-button-box {
&button {
width: 220px;
}
}
.details-butn-sub-wrapper-r {
width: 50%;
}
.details-butn-sub-wrapper-l {
display: flex;
justify-content: flex-end;
width: 50%;
padding-right: 5px;
}
.detail-text {
//
}
.casetag-text {
font-size: 0.88rem;
}
.detail-card-casetags {
display: flex;
flex-direction: column;
flex-grow: 3;
}
.arrow-wrapper > svg {
margin-bottom: 4px !important;
margin-right: 4px !important;
margin-left: 5px;
}
.details-back-wrapper {
margin-left: 4px;
font-size: 1.04rem;
padding-top: 3px;
}

View File

@@ -0,0 +1,18 @@
.foo {
height: 300px;
&:hover {
border: 2px solid #f27300;
}
}
.new-case-button {
height: 50px;
width: 150px;
background-color: #1a76c7;
color: #fff;
border-radius: 10px;
border-style: none;
&:hover {
border: 2px solid #f27300;
}
}

View File

@@ -0,0 +1,93 @@
.type-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-left: auto;
margin-right: auto;
width: 900px;
}
.type-row {
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
margin-left: auto;
margin-right: auto;
}
.type-col {
display: flex;
flex-direction: column;
width: 50%;
margin: 12px;
}
.create-case-taginput {
display: flex;
height: 35px;
}
.case-tag-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding-top: 8px;
}
.tag-container {
height: 30px;
margin-top: 8px;
margin-bottom: 8px;
align-items: center;
}
.tag-box {
background-color: rgba(224, 224, 224, 0.421);
display: flex;
border-radius: 8px;
margin: 4px 4px;
padding: 6px;
}
.tag-ex-box {
display: flex;
}
.tag-ex {
cursor: pointer;
margin-top: -6px;
font-weight: 500;
font-size: 0.98rem;
height: 20px;
}
.casetag-name {
font-size: 0.94rem;
padding: 0px 8px;
}
.casetype-button-container {
margin-top: 20px;
margin-right: 16px;
display: flex;
flex-direction: column;
align-items: flex-end;
width: 100%;
}
.radio-wrapper {
padding-left: 8px;
margin-top: -3px;
border: 1px solid #eaeaea;
border-radius: 10px;
}
.casetype-error {
font-size: 0.84rem;
margin-top: 2px;
margin-left: 2px;
padding-left: 6px;
color: red;
}

View File

@@ -0,0 +1,249 @@
.contactContainer {
display: flex;
flex-direction: column;
margin-top: 20px;
margin-left: auto;
margin-right: auto;
height: 600px;
}
.contactInner {
display: flex;
flex-direction: row;
height: 350px;
width: 100%;
margin-top: 15px;
background-color: #f5fcfc;
}
.contactLeftColumn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 50%;
height: 350px;
border-radius: 40px 0px 40px 0px;
border: 1px solid #1093ff;
}
.contactRightColumn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 350px;
width: 50%;
border-radius: 40px 0px 40px 0px;
border: 1px solid #2196f3;
}
.contact-link {
font-size: 1rem;
letter-spacing: 0.2rem;
color: #fff;
text-decoration: none;
padding-right: 5px;
}
.contact-link-two {
font-size: 0.95rem;
letter-spacing: 0.3rem;
color: #fff;
text-decoration: none;
padding-right: 25px;
}
.contact-link-twotwo {
font-size: 0.95rem;
letter-spacing: 0.92rem;
color: #fff;
text-decoration: none;
padding-right: 25px;
}
.contactLinkThree {
letter-spacing: 1rem;
text-decoration: none;
}
.contactLinkFour {
letter-spacing: 1rem;
text-decoration: none;
color: black;
}
.contactLinkFive {
font-size: 0.95rem;
letter-spacing: 0.45rem;
color: #acacac;
text-decoration: none;
margin-left: 10px;
padding-left: 35px;
}
.contactLinkSix {
font-size: 0.95rem;
letter-spacing: 0.1rem;
color: #fff;
text-decoration: none;
}
.contact-info-box {
display: flex;
flex-direction: column;
justify-content: space-around;
width: 60%;
height: 80%;
}
.contact-detail-one {
display: flex;
flex-direction: row-reverse;
height: 38px;
margin: 12px 0px;
background-color: rgb(56, 56, 233);
}
.contact-detail-two {
display: flex;
flex-direction: row-reverse;
height: 38px;
margin: 12px 0px;
background-color: hsl(17, 100%, 61%);
letter-spacing: 0.2rem;
}
.contact-detail-twotwo {
display: flex;
flex-direction: row-reverse;
height: 38px;
margin: 12px 0px;
padding-right: 18px;
background-color: hsl(17, 100%, 61%);
}
.contact-detail-three {
display: flex;
flex-direction: row-reverse;
height: 40px;
margin: 12px 0px;
background-color: rgb(93, 206, 255);
letter-spacing: 0.5rem;
}
.contact-detail-threethree {
display: flex;
flex-direction: row-reverse;
height: 40px;
margin: 12px 0px;
background-color: rgb(93, 206, 255);
letter-spacing: 0.5rem;
}
.contactDetailFour {
width: 40%;
background-color: orange;
color: #000;
}
.contactDetailFive {
display: flex;
flex-direction: column;
width: 100%;
background-color: yellow;
letter-spacing: 0.6rem;
justify-content: center;
align-items: center;
width: 70%;
}
.contactDetailSix {
background-color: #acacac;
display: flex;
flex-direction: column;
align-items: flex-end;
}
.imgBox {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
.imgBoxOne {
display: flex;
width: 30%;
}
.imgBoxTwo {
display: flex;
width: 30%;
}
.imgBoxThree {
display: flex;
align-items: center;
width: 40%;
}
.threeInner {
display: flex;
margin-left: 5px;
height: 300px;
width: 130px;
border-radius: 0px;
overflow: hidden;
opacity: 0.7;
background: linear-gradient(to left, rgba(245, 246, 252, 0.52), cyan);
}
@media only screen and (max-width: 430px) {
.contactInner {
display: flex;
flex-direction: column;
}
.contactLeftColumn {
width: 100%;
}
.contactRightColumn {
width: 100%;
height: 500px;
margin-top: 25px;
}
.contactInfoBox {
height: 400px;
}
.contactDetailTwo {
align-items: flex-end;
}
.contactDetailThree {
align-items: center;
}
.nav-link-three {
color: black;
letter-spacing: 1rem;
text-decoration: none;
letter-spacing: 1.2rem;
padding-right: 20px;
}
.nav-link-threthree {
color: black;
letter-spacing: 1rem;
text-decoration: none;
padding-right: 24px;
}
.contact-linklink {
font-size: 1rem;
letter-spacing: 0.6rem;
color: #fff;
text-decoration: none;
padding-right: 16px;
}
}

View File

@@ -0,0 +1,17 @@
$font-family-sans-serif: "Roboto", "Karla", "Great Vibes", "Helvetica Neue",
system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", "Liberation Sans",
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji" !default;
$font-family-italic: "Roboto Regular 400 Italic";
$navbar-padding-y: 1.2rem;
$navbar-padding-x: 2rem;
$headings-margin-bottom: 1rem;
$primary: #1a76c7;
$haq-blue: #edf4f5;
$text-dark: #000;
$text-medium: #767676;
$text-light: #a9a9a9;
$ultra-light-accent: #e4e4e4;
$accent-orange: #f27300;
$link-decoration: none;
$link-hover-decoration: underline;

View File

@@ -0,0 +1,193 @@
@use "sass:map";
.dashboard-header {
display: flex;
gap: 1rem;
justify-content: flex-end;
flex-direction: column;
margin: 30px 0px 0px 0px;
}
.dash-promo-first-box {
display: flex;
flex-direction: column;
justify-self: center;
align-items: center;
margin-top: 8px;
padding: 8px 32px;
}
.user-name-span {
font-weight: 500;
}
.dash-promo-first-wrap {
padding: 8px 0px;
background-color: var(--barely-there-accent);
display: flex;
flex-direction: column;
border: 1px solid orange;
border-radius: 5px;
}
.dash-promo-first-textwrap {
display: flex;
flex-direction: column;
margin: auto;
}
.dash-welcome-text {
line-height: 1.7;
font-family: Roboto;
font-weight: 300;
margin-top: 6px !important;
}
.dash-promo-first-text {
font-size: 0.94rem;
letter-spacing: 0.03rem;
font-family: Roboto;
font-weight: 300;
margin-bottom: 2px;
padding: 0px 12px;
width: 680px;
color: #3b3b3b;
&.inline-text {
display: flex;
flex-wrap: wrap;
}
}
.dash-clicknow-span {
cursor: pointer;
color: blue;
}
.dash-heading-text {
font-size: 1.3rem;
}
.create-new-box {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
margin-bottom: 10px;
}
.view-all-link {
margin-right: 10px;
text-decoration: none;
}
.view-all-link-box {
display: flex;
width: 100%;
justify-content: flex-end;
}
.arrow-wrapper > svg {
color: #fa6400;
margin-bottom: 3px;
}
.user-name-container2 {
margin-top: -4px;
margin-left: 10px;
font-weight: 500;
font-size: 1.1rem;
color: var(--font-light);
}
.dash-divider {
margin-top: 8px;
}
.stats-container {
margin: 12px 0px;
}
.stats-row-box {
display: flex;
flex-direction: row;
align-items: center;
border: 1px solid orange;
border-radius: 10px;
background-color: #f5f5f5;
margin: 12px 0px;
padding: 0px 6px;
height: 42px;
border-radius: 6px;
}
.stats-unit-wrapper {
display: flex;
flex-direction: row;
margin-left: 16px;
}
.stats-text {
margin-right: 8px;
margin-bottom: 0px;
}
.stats-icon-wrapper {
margin: 0px 10px 2px 10px;
}
.dashboard-filter {
display: flex;
gap: 1rem;
justify-content: flex-end;
flex-direction: row;
}
@media only screen and (max-width: 860px) {
.dash-promo-first-text {
width: 500px;
}
}
@media only screen and (max-width: 430px) {
.dashboard-header {
display: flex;
gap: 1rem;
justify-content: center;
margin: 30px 0px 0px 0px;
align-items: center;
margin-left: auto;
margin-right: auto;
padding: 12px 0px;
width: 280px;
background-color: var(--background-blue);
border-radius: 15px;
}
.stats-container {
height: 100%;
}
.stats-row-box {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 12px;
margin-bottom: 0px;
padding: 0px 6px;
background-color: #fff;
height: 100%;
border-radius: 6px;
}
.stats-unit-wrapper {
display: flex;
flex-direction: row;
background-color: var(--barely-there-accent);
width: 290px;
margin: 4px 0px;
padding: 8px 0px;
border-radius: 15px;
justify-content: center;
}
.notification-container {
background-color: red;
}
}

View File

@@ -0,0 +1,53 @@
.requestdemo-para {
font-size: 1.3rem;
margin-top: -12px;
margin-bottom: 30px;
}
.demo-request-form {
width: 760px;
}
.requestdemo-tagline {
margin-bottom: 30px;
}
.demo-button-box {
display: flex;
flex-direction: column;
width: 85%;
justify-content: end;
align-items: end;
}
.demo {
width: 160px;
}
.demo-link-span {
margin-right: 10px;
}
@media only screen and (max-width: 430px) {
.demo-request-form {
width: 360px;
}
.demo-request-form {
display: flex;
flex-direction: column;
width: 340px;
align-items: center;
justify-content: center;
margin: auto;
}
.demo-page-row {
display: flex;
flex-direction: column;
width: 340px;
}
.demo-button-box {
width: 95%;
}
}

View File

@@ -0,0 +1,38 @@
.det-card-card-outer {
display: flex;
flex-direction: column;
border: 1px solid var(--ultra-light-accent);
background-color: #fcfcfc;
padding: 5px 0px;
border-radius: 10px;
min-width: 700px;
//box-shadow: rgba(204, 216, 228, 0.2) 0px 8px 24px;
}
.detail-card-row {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
height: 100%;
}
.detail-card-col {
display: flex;
flex-direction: column;
min-height: 200px;
width: 32%;
margin: 4px 4px;
padding: 0px 12px;
}
.detail-card-detail {
width: 100%;
height: 60px;
margin-top: 10px;
}
.detail-subhead {
letter-spacing: 1px;
font-weight: 500;
}

View File

@@ -0,0 +1,532 @@
.docedit-container {
display: flex;
flex-direction: column;
align-items: center;
}
.edit-divider-spacer {
margin-bottom: 20px !important;
}
.docedit-outer-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
margin: auto;
}
.docedit-header-text {
margin-top: 50px;
padding-top: 38px;
color: #c5c5c5;
font-size: 24px;
}
.edit-header-wrapper {
width: 700px;
}
.doc-editing-sub-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
margin-bottom: 25px;
}
.request-editing-sub-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
margin-bottom: 25px;
}
.req-editing-sub-wrapper {
display: flex;
flex-direction: column;
}
.doc-editing-instructions-header {
font-family: Roboto;
text-decoration: underline;
text-align: center;
font-size: 1.3rem;
font-weight: 500;
letter-spacing: 0.013rem;
}
.doc-edit-subheader {
display: flex;
flex-direction: row;
margin-bottom: 20px;
}
.req-item-outer-div {
margin-bottom: 10px;
&.row {
display: flex;
flex-direction: row;
}
}
.req-item-inner-div {
min-width: 680px;
margin: 6px 0px;
}
.doc-editing-button-container {
display: flex;
flex-direction: column;
align-items: flex-end;
margin-top: 0.6rem;
}
.req-editing-button-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-end;
margin-top: 0.6rem;
margin-bottom: 24px;
}
.req-edit-box {
width: 100%;
}
.edit-back-button {
height: 50px;
width: 215px;
margin-right: 20px;
border: 1px solid #1a76c7 !important;
background-color: #fff !important;
color: #1a76c7 !important;
&:hover {
background-color: var(--background-blue) !important;
}
}
.edit-save-button {
height: 50px;
width: 150px;
margin-top: -2px;
}
.generate-button-box {
display: flex;
flex-direction: row;
height: 50px;
margin-bottom: 8px;
}
.docedit-super-row {
display: flex;
flex-direction: row;
min-height: 55px;
letter-spacing: 0.02rem;
}
.docedit-header-row {
display: flex;
flex-direction: row;
height: 230px;
margin-bottom: 12px;
color: #000000;
font-weight: 500;
overflow: hidden;
}
.docedit-header-col1 {
display: flex;
flex-direction: column;
width: 20%;
height: 100%;
color: #000;
font-weight: 500;
justify-content: left;
align-items: flex-start;
padding-left: 15px;
}
.docedit-header-col2 {
display: flex;
flex-direction: column;
width: 60%;
height: 210px;
margin-bottom: 8px;
color: #000;
font-weight: 500;
}
.docedit-header-col3 {
display: flex;
flex-direction: column;
align-items: center;
margin: auto;
width: 20%;
height: 100%;
color: #373737;
}
.header-middle-row1 {
display: flex;
flex-direction: row;
margin: 4px 0px;
padding: 4px 0px;
color: #373737;
letter-spacing: 0.02rem;
}
.header-middle-row2 {
display: flex;
flex-direction: row;
margin: 4px 0px;
padding: 4px 0px;
justify-content: space-between;
color: #373737;
letter-spacing: 0.02rem;
}
.header-middle-row3 {
display: flex;
flex-direction: row;
margin: 4px 0px 12px 0px;
padding: 4px 0px;
justify-content: space-between;
color: #373737;
letter-spacing: 0.02rem;
}
.middle-row-inner {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.content-spacer {
width: 140px;
}
.content-spacer2 {
width: 116px;
}
.header-middle-row4 {
display: flex;
flex-direction: row;
margin: 4px 0px 6px 0px;
padding: 4px 0px;
justify-content: space-between;
color: #373737;
letter-spacing: 0.02rem;
}
.header-middle-row5 {
display: flex;
flex-direction: row;
margin-top: 4px;
padding: 4px 0px;
color: #373737;
letter-spacing: 0.02rem;
}
.aginum {
color: #373737;
letter-spacing: 0.02rem;
}
.aginum-agin {
color: #373737;
letter-spacing: 0.02rem;
margin-top: 5px;
}
.docedit-uppercol1 {
width: 20%;
color: #373737;
}
.centered-text {
text-align: center;
margin-bottom: 30px;
}
.plain-text {
font-weight: 400 !important;
}
.docedit-uppercol2 {
display: flex;
flex-direction: column;
width: 60%;
font-weight: 500;
color: #000000;
}
.docedit-uppercol-divider {
width: 470px;
border-bottom: 2px solid #c3c3c3;
}
.docedit-uppercol3 {
width: 20%;
color: #4e4e4e;
}
.left-caption-col {
display: flex;
flex-direction: column;
padding-top: 6px;
width: 50%;
color: #4e4e4e;
}
.right-caption-col {
display: flex;
flex-direction: column;
align-items: flex-end;
width: 50%;
margin: 25px 15px;
color: #4e4e4e;
}
.doc-editing-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 60%;
margin: 6px auto;
color: #4e4e4e;
}
.casenum-box {
margin-left: 15px;
padding-left: 15px;
color: #4e4e4e;
letter-spacing: 0.02rem;
}
.peez {
display: flex;
flex-direction: row;
justify-content: center;
padding-right: 35px;
margin-right: 20px;
color: var(--font-light);
letter-spacing: 0.02rem;
}
.deez {
display: flex;
flex-direction: row;
justify-content: center;
padding-right: 23px;
margin-right: 20px;
margin-top: 5px;
color: var(--font-light);
letter-spacing: 0.02rem;
}
.caption-box {
word-spacing: 20px;
color: #4e4e4e;
}
.topline-box {
padding-top: 8px;
color: #4e4e4e;
}
.topline-box2 {
margin-top: 20px;
padding-top: 8px;
color: #4e4e4e;
}
.edit-ele-input {
color: #7d7d7d;
}
.request-text-up {
color: #a2a2a2;
margin-top: 5px;
text-align: justify;
&.reqq {
max-width: 24px;
}
}
.doced-request-text-up {
color: #a2a2a2;
margin-top: 5px;
margin-left: 8px;
margin-bottom: 16px;
text-align: justify;
&.reqq {
max-width: 24px;
}
}
.request-text-up-row {
display: flex;
flex-direction: row;
color: #a2a2a2;
margin-top: 6px;
padding: 0px 5px;
text-align: justify;
line-height: 2;
}
.request-text-number-col {
min-width: 22px;
margin-right: 0px;
margin-top: 3px;
}
.request-text-number-col-low {
min-width: 22px;
margin-right: 2px;
margin-top: 6px;
}
.outer-div {
border: 1px solid #d1d1d1;
border-radius: 5px;
border-radius: 10px;
padding: 0px 5px;
}
.pleading-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
letter-spacing: 0.02rem;
padding: 0px 5px;
margin: auto;
text-decoration: underline;
margin-bottom: 20px;
font-weight: 500;
color: #000;
}
.req-edit-comesnow {
line-height: 2;
text-align: justify;
max-width: 700px;
}
.req-edit-comesnow-box {
min-width: 600px;
margin-bottom: 18px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.scroll-button {
position: fixed;
top: 250px;
right: 70px;
width: 150px;
height: 50px;
background-color: #fff !important;
color: #1a76c7 !important;
border-color: #1a76c7 !important;
&:hover {
border-color: #1a76c7 !important;
background-color: #ffeee3 !important;
}
}
.create-gen-button {
color: #fff;
&:hover {
background-color: #1664a9;
border: none !important;
}
}
.numbering-container {
color: #a2a2a2;
margin-bottom: 6px;
}
.docedit-errstate-container {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 50px;
}
.reqedit-report-container {
width: 95%;
margin-top: -16px;
margin-left: 16px;
}
.docedit-report-link {
font-size: 0.95rem;
color: #fa6400;
}
.docedit-report-link:hover {
text-decoration: underline;
cursor: pointer;
}
.request-item-outer-div {
min-width: 700px;
display: flex;
flex-direction: row;
margin: 16px 0px 8px 0px;
}
@media only screen and (max-width: 1140px) {
.scroll-button {
right: 30px;
}
}
@media only screen and (max-width: 1080px) {
.scroll-button {
right: 0px;
}
}
@media only screen and (max-width: 1080px) and (min-width: 900px) {
.request-editing-sub-wrapper {
max-width: 650px;
}
.request-text-up-row {
max-width: 650px;
}
.req-edit-wrapper {
max-width: 650px;
}
.req-item-inner-div {
min-width: 620px;
max-width: 650px;
}
.reqedit-report-container {
margin-left: 0px;
margin-top: -16px;
}
}
.prelim-statement-container {
margin-bottom: 8px;
padding-left: 6px;
}
.docedit-prelim-text {
line-height: 2;
}

View File

@@ -0,0 +1,142 @@
.doc-list-header {
display: flex;
flex-direction: column;
justify-content: center;
margin: 25px 0px;
background-color: #e6e6e653;
border-radius: 10px;
height: 85px;
padding: 0px 0px 0px 15px;
border: 1px solid orange;
}
.doc-header-text {
margin-bottom: -7px !important;
margin-left: 6px;
letter-spacing: -0.6px;
font-weight: 400;
}
.doclist-upper-wrapper {
display: flex;
flex-direction: row;
}
.doclist-toggle-box {
margin-left: auto;
margin-right: 25px;
min-width: 120px;
padding-top: 4px;
}
.doclist-select-box {
min-width: 100px;
}
.nodocs-text-container {
border-top: 1px solid #dcd9d9 !important;
margin-top: 6px;
margin: 15px 15px;
padding: 15px 25px;
}
.nodocs-text {
padding: 3px 5px;
font-weight: 500;
}
.nodocs-light-text {
padding: 3px 5px;
}
.nodocs-list-item {
padding: 3px 0px;
}
.document-list-upper-wrapper {
display: flex;
flex-direction: row;
width: 100%;
}
.list-upper-left {
display: flex;
flex-direction: row;
justify-content: right;
width: 95%;
}
.doc-list-subhead-container {
display: flex;
justify-content: center;
align-items: center;
margin-left: 12px;
margin-top: auto;
margin-bottom: auto;
padding-bottom: 12px;
}
.doc-list-subhead {
font-family: Roboto;
font-size: 1.18rem;
font-weight: 400;
}
.toggle-outer {
display: flex;
flex-direction: column;
border: 1px solid var(--accent-orange-light);
margin-right: 12px;
border-radius: 10px;
background-color: var(--background-blue);
}
.toggle-box {
display: flex;
flex-direction: row;
width: 200px;
height: 15px;
justify-content: space-evenly;
}
.toggle-header {
display: flex;
justify-content: center;
padding-right: 7px;
height: 28px;
}
.doclist-toggle-link {
color: blue;
}
.doclist-toggle-link:hover {
cursor: pointer;
color: orange;
text-decoration: underline;
}
.document-list-lower-wrapper {
display: flex;
flex-direction: column;
width: 100%;
margin-bottom: 3rem;
}
.user-name-container {
margin-top: 8px;
margin-left: 7px !important;
color: var(--font-light);
font-weight: 500;
font-size: 1.1rem;
}
.content-container {
padding: 0rem 1rem;
}
@media only screen and (max-width: 1120px) {
.list-upper-left {
width: 980px;
}
}

View File

@@ -0,0 +1,23 @@
.outer-div {
border: 1px solid rgb(220, 220, 220);
border-radius: 5px;
}
.outer-div-after {
border: none;
display: none;
}
.edit-ele-input {
border: none;
width: 100%;
overflow: hidden;
min-height: 200px;
resize: none;
text-align: justify;
line-height: 2;
}
textarea {
text-align: justify;
}

View File

@@ -0,0 +1,191 @@
.features-container {
margin-bottom: 38px;
}
.features-top-wrapper {
display: flex;
flex-direction: row;
margin: 28px 5px;
padding: 0px 8px;
width: 100%;
height: 350px;
}
.features-header-text {
display: flex;
flex-direction: row;
margin-top: 8px;
margin-left: 8px;
padding-left: 7px;
}
.features-asterisk {
font-size: 12px;
}
.features-disclosure {
font-size: 12px;
float: right;
margin-right: 32px;
}
.features-bottom-wrapper {
display: flex;
flex-direction: row;
margin: 42px 5px 8px 5px;
padding: 0px 8px;
width: 100%;
height: 350px;
}
.features-top-left {
display: flex;
flex-direction: column;
padding: 6px 8px;
width: 45%;
height: 100%;
}
.features-top-right {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: auto;
padding: 6px 8px;
width: 55%;
background-color: var(--haq-blue);
border-radius: 15px;
height: 100%;
}
.features-bottom-left {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: auto;
padding: 6px 8px;
width: 55%;
background-color: var(--haq-blue);
border-radius: 15px;
height: 100%;
}
.features-bottom-right {
display: flex;
flex-direction: column;
padding: 18px 8px;
width: 45%;
height: 100%;
}
.features-image-one {
display: flex;
flex-direction: column;
margin-right: 10px;
height: 300px;
float: right;
border-radius: 15px;
}
.features-list-item {
width: 94%;
text-align: justify;
font-size: 1.05rem;
margin: 18px 0px;
}
.features-list-item-lower {
width: 94%;
text-align: justify;
font-size: 1.05rem;
margin: 16px 0px;
}
.features-top-mobile-img {
display: none;
}
@media only screen and (max-width: 430px) {
.features-container {
height: 1000px;
}
.features-top-wrapper {
flex-direction: column;
height: 480px;
margin-bottom: 12px;
}
.features-bottom-wrapper {
margin: 0px 0px;
padding-left: 0px;
flex-direction: column;
}
.features-top-left {
margin-top: 12px;
width: 100%;
}
.features-bottom-left {
margin-top: 12px;
width: 100%;
}
.features-top-right {
width: 100%;
}
.features-bottom-right {
width: 100%;
}
.features-bottom-left {
width: 100%;
}
.features-header-text {
letter-spacing: -0.09rem;
margin-top: 6px;
padding: 0px;
}
.features-list {
padding: 0px;
margin-left: 5px;
}
.features-list-item {
width: 98%;
text-align: justify;
font-size: 0.94rem;
margin: 8px 0px;
}
.features-list-item-lower {
width: 98%;
text-align: justify;
font-size: 0.94rem;
margin: 8px 0px 8px 8px;
}
.features-top-right {
display: none;
}
.features-top-mobile-img {
display: flex;
}
.features-image-one {
display: flex;
flex-direction: column;
margin-right: 0px;
height: 100%;
width: 100%;
float: right;
border-radius: 15px;
}
}

View File

@@ -0,0 +1,100 @@
.footer-container {
display: flex;
flex-direction: column;
border: 1px solid orange;
border-radius: 8px;
background-color: var(--bluest-blue);
}
.footer-upper-box {
display: flex;
flex-direction: row;
height: 150px;
width: 100%;
}
.footer-right-box {
display: flex;
flex-direction: row;
margin-top: 12px;
padding: 10px 20px;
width: 75%;
}
.footer-left-box {
display: flex;
justify-content: center;
align-items: center;
padding: 10px 20px;
width: 25%;
}
.footer-logo {
height: 80px;
}
.footer-left-inner {
width: 100%;
height: 95%;
border-right: 1px solid #fff;
}
.footer-lower-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
//border-top: 1px solid #ffd7b1;
height: 50px;
width: 100%;
}
.footer-item {
font-weight: 500;
color: #fff;
margin: 0px 10px;
padding: 0px 10px;
}
.footer-item-link {
letter-spacing: 0.11rem;
font-weight: 500;
color: #fff;
margin: 0px 10px;
padding: 0px 10px;
cursor: pointer;
}
/*
background-color: var(
--bluest-blue
); //#4675c3; // before Ali: #4e6a98; // #18263c; //#a7b1c0 #22416f //#22416f
*/
@media only screen and (max-width: 430px) {
.footer-left-box {
width: 40%;
}
.footer-left-inner {
border-right: none;
}
.footer-right-box {
width: 60%;
flex-direction: column;
}
.footer-logo {
height: 60px;
}
.footer-item-link {
font-size: 0.87rem;
margin-bottom: 16px;
}
.footer-item {
font-size: 0.87rem;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
.doc-list-header {
display: flex;
flex-direction: column;
justify-content: center;
margin: 25px 0px;
background-color: #e6e6e653;
border-radius: 10px;
height: 85px;
padding: 0px 0px 0px 16px;
}
.doc-header-text {
margin-bottom: -7px !important;
margin-left: 6px;
letter-spacing: -0.6px;
font-weight: 400;
}
.user-name-container {
margin-top: 8px;
margin-left: 7px;
color: var(--font-light);
font-weight: 500;
}
.steps-wrapper {
display: flex;
flex-direction: column;
width: 100%;
padding: 0px 10px;
background-color: #e6e6e653;
border-radius: 10px;
}
.steps-text-wrapper {
line-height: 1.5;
margin-bottom: 8px;
padding: 6px;
text-align: justify;
}
.fooze {
display: flex;
flex-direction: row;
}
.steps-upper-right {
display: flex;
flex-direction: column;
width: 50%;
}
.steps-upper-left {
display: flex;
flex-direction: column;
width: 50%;
}
.image-right-one {
display: flex;
flex-direction: row;
margin-bottom: 16px;
}
.image-jhon {
display: flex;
flex-direction: column;
width: 12%;
}
.mini-break {
margin: 6px 0px;
}
.image-moun {
display: flex;
flex-direction: column;
width: 80%;
}
.howto-hand-img {
border-radius: 10px;
width: 424px;
}
.bold-span {
font-weight: 500;
}
.barzee {
padding-right: 20px;
margin-top: -12px;
}

View File

@@ -0,0 +1,32 @@
@import "custom";
@import "~bootstrap/scss/bootstrap";
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto:ital@1&display=swap ");
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Karla&family=Roboto&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@100;300&display=swap");
@import "app";
@import "text-input";
@import "progress-bar";
@import "buttons";
@import "cards";
@import "case-details";
@import "caselist-page";
@import "upload-modal";
@import "navbar";
@import "navpanel";
@import "account-page";
@import "dashboard";
@import "docedit-page";
@import "doclist-page";
@import "edit-element";
@import "radio-group";
@import "login";
@import "password-reset";
@import "footer";
@import "footer";
@import "modals";

Some files were not shown because too many files have changed in this diff Show More