Merge pull request #179 from kjannette/analy

Analy
This commit is contained in:
S Jannette
2024-03-23 03:08:04 -05:00
committed by GitHub
10 changed files with 680 additions and 388 deletions

View File

@@ -1,5 +1,10 @@
import React, { useState, useEffect } from "react";
import { checkActionCode, confirmPasswordReset, getAuth, sendPasswordResetEmail } from "firebase/auth";
import {
checkActionCode,
confirmPasswordReset,
getAuth,
sendPasswordResetEmail,
} from "firebase/auth";
import Button from "../../pageElements/Button";
import { useSearchParams } from "react-router-dom";
@@ -75,14 +80,15 @@ const PasswordResetPage = () => {
} 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" ? <>
{resetMode === "enterEmail" ? (
<>
<div className="password-reset-header">
<h2 className="password-reset-header-text">
Enter Account Email
@@ -123,11 +129,15 @@ const PasswordResetPage = () => {
) : null}
{done && (
<div className="success-container">
<p className="reset-sent-text">A password reset link was sent.</p>
<p className="reset-sent-text">
A password reset link was sent.
</p>
</div>
)}
</> : null}
{resetMode === "resetPassword" ? <>
</>
) : null}
{resetMode === "resetPassword" ? (
<>
<div className="password-reset-header">
<h2 className="password-reset-header-text">
Enter New Password
@@ -159,7 +169,10 @@ const PasswordResetPage = () => {
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={isBusy}
></input>
<label htmlFor="confirmPasswordInput" className="form-label">
<label
htmlFor="confirmPasswordInput"
className="form-label"
>
Confirm password
</label>
</div>
@@ -182,7 +195,9 @@ const PasswordResetPage = () => {
) : null}
{!done && isCodeValid !== null && !isCodeValid && (
<div className="success-container">
<p className="reset-sent-text">Bad or already used password reset code.</p>
<p className="reset-sent-text">
Bad or already used password reset code.
</p>
</div>
)}
{done && (
@@ -190,7 +205,8 @@ const PasswordResetPage = () => {
<p className="reset-sent-text">Password changed.</p>
</div>
)}
</> : null}
</>
) : null}
</div>
</div>
</div>

View File

@@ -83,6 +83,7 @@ const CaseListPage = ({ perPage }) => {
</div>
);
};
const dropDownOptions = [
{ label: "Case name a - z", value: "caption" },
{ label: "Case name z - a", value: "caption2" },
@@ -159,11 +160,17 @@ const CaseListPage = ({ perPage }) => {
</>
) : (
<div className="nodocs-text-container">
<p> You have not created any cases yet.</p>
<p> Please create your first case.</p>
<p>
To get started, click "Create New Case" and enter case
information.
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>
)

View File

@@ -15,21 +15,26 @@ 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 isDashboard = true;
console.log("group on dashbaord page", group);
console.log("auth in dashoabrd page", auth);
useEffect(() => {
if (count.current == null) {
@@ -177,6 +182,31 @@ const Dashboard = () => {
<div className="user-name-container2">{group.firm}</div>
</div>
<div className="app-divider dash-divider" />
{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">
Welcome to Novodraft, attorney {group.lastName}. We are
excited to introduce you to our application. Review the
step-by-step user guide by clicking "How-to" in the navigation
bar.
</div>
<div className="dash-promo-first-text">
Your user name for login is:{" "}
<span className="user-name-span">{group.email}</span>. Your
temporary password is{" "}
<span className="user-name-span">{group.tpe}</span>. You
should change it now, by clicking here.
</div>
</div>
</div>
</div>
<div className="app-divider dash-divider" />
</>
) : null}
<div className="stats-container">
<div className="stats-row-box">
<div className="stats-unit-wrapper">

View File

@@ -1,30 +1,206 @@
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { collection, setDoc, doc } from "firebase/firestore";
import { db } from "../../firebase";
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 handleNavigate = () => {
navigate("/signup");
const apiUrl =
process.env.NODE_ENV === "development"
? process.env.REACT_APP_API_DEV
: process.env.REACT_APP_API_PROD;
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 userData = {
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,
};
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}`);
}
}
async function getFocusData(code) {
console.log("code in getFocusData", code);
try {
const response = await fetch(`${apiUrl}/v1/get-focused-data/${code}`, {
method: "GET",
});
const res = await response.json();
console.log("res in getFocusData", res);
return res;
} catch (err) {
console.log("Error in get rqst:", err);
}
}
async function handleProcessPromoSubscription(code) {
console.log("code in handleProcessPromoSubscription", code);
setIsBusy(true);
setShowPromoModal(false);
if (!code) {
return;
}
const userData = await getFocusData(code);
console.log("userData in returned from getFocusData", userData);
const email = userData[0].email;
const password = userData[0].password;
if (email && password) {
console.log("123 email, password", email, password);
handlePromoSignup(userData, email, password);
}
}
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;
console.log("subscriptionPeriodStart", subscriptionPeriodStart);
const subscriptionPeriodEnd = today;
const subscriptionId = uuidv4();
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
dataValues = { ...dataValues };
const isPromotionalMebership = true;
console.log("userCredential", userCredential);
const res = await saveUserData(
user.uid,
dataValues,
email,
password,
customerId,
subscriptionCreated,
subscriptionPeriodStart,
subscriptionPeriodEnd,
subscriptionId,
isPromotionalMebership
);
console.log("res from saveUserData in handlePromoSignup", res);
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 = () => {
setShowPromoModal(true);
};
const handleLogin = () => {
navigate("/login");
};
const handleCancelModal = () => {
setShowPromoModal(!showPromoModal);
navigate("/signup");
};
const handleDemo = () => {
const supReq = null;
navigate("/requestpage/supReq");
};
return (
<>
const homeContent = (
<div className="homepage-container">
<section
className="section-1"
@@ -37,13 +213,11 @@ const HomePage = () => {
<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">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-mobile">AI-Assisted Legal Drafting</h1>
<h1 className="heading-2">
Draft documents faster <i>and </i> smarter.
Draft documents faster <i>and </i> smarter
</h1>
</div>
<div className="mobile-lower-header-1">
@@ -61,14 +235,14 @@ const HomePage = () => {
<Button
className="primary-button homepage-button"
labelText="Try It For Free"
onClick={handleNavigate}
onClick={handleTryItButton}
/>
</div>
</div>
</div>
<p className="text-block-1">
Discovery requests done dramatically fast discovery
responses ready in minutes
Discovery requests done dramatically fast discovery responses
ready in minutes.
</p>
<p className="text-block-two">
Reclaim hours every week. Spend time growing your practice, or
@@ -78,7 +252,7 @@ const HomePage = () => {
<Button
className="primary-button homepage-button"
labelText="Try It For Free"
onClick={handleNavigate}
onClick={handleTryItButton}
/>
</div>
</div>
@@ -105,10 +279,10 @@ const HomePage = () => {
</div>
<div className="home-col-50">
<div className="pad-2">
<h2 className="heading-2">Made by Attorneys, for Attorneys</h2>
<h2 className="heading-2">Made by Attorneys, for Attorneys.</h2>
<div className="text-block-2-box">
<p className="text-block-2">
Legal tech built by people that get it.
Legal tech developed by people that understand what you need.
</p>
</div>
@@ -116,19 +290,19 @@ const HomePage = () => {
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.
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.
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.
boosts bland, boilerplate objections. Configurable for level of
authoritative citation and for infering factual context from
pleadings and briefs. Use it to augment your drafting as much or
as little as you prefer.
</p>
<div className="home-link-box">
<a className="link">
@@ -174,9 +348,12 @@ const HomePage = () => {
<div className="home-card-text-box">
<p className="card-texts">
Generate discovery requests with AI at your service, edit or
add special language with tags. Concise yet comprehensive
demands, done in minutes and downloadable as .docx.
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>
@@ -201,9 +378,9 @@ const HomePage = () => {
<div className="home-card-text-box">
<p className="card-texts">
Novodraft's AI powers responses with compelling arguments
about what you choose to disclose and withhold. You'll
finish in minutes with a downloadable .docx file.
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>
@@ -227,8 +404,8 @@ const HomePage = () => {
<div className="home-card-text-box">
<p className="card-texts">
Your dashboard displays important deadlines, alerts, and can
configure to auto-draft documents and remind you to serve
them.
be configured to auto-draft documents with reminders for tasks
such as service or hearings.
</p>
</div>
</div>
@@ -277,8 +454,25 @@ const HomePage = () => {
</div>
</div>
</section>
{showPromoModal ? (
<ConfirmModal
titleText="Enter Promo 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
);
};

View File

@@ -22,6 +22,7 @@ const ConfirmModal = ({
isPromoModal,
cancelButtonText,
}) => {
const [inputValue, setInputValue] = useState("");
const title = titleText ? titleText : "Confirmation";
return (
@@ -53,11 +54,11 @@ const ConfirmModal = ({
<TextInput
key={"captionTwo"}
name={"captionTwo"}
value={issueText}
value={inputValue}
message={"data.captionTwo.message"}
onChange={(e) => setIssueText(e.target.value)}
onChange={(e) => setInputValue(e.target.value)}
label={
issueText
inputValue
? ""
: isPromoModal
? "Enter your promotional code"
@@ -79,7 +80,7 @@ const ConfirmModal = ({
<Button
disabled={isBusy}
className="primary-button"
onClick={onConfirm}
onClick={() => onConfirm(inputValue)}
labelText={buttonLabelText}
/>
</Modal.Footer>

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState } from "react";
import Col from "react-bootstrap/Col";
import Form from "react-bootstrap/Form";
import Row from "react-bootstrap/Row";
@@ -75,9 +75,9 @@ const SignupPage = () => {
}
};
const handleConfirmPromoCode = () => {
if (issueText === marchEmailPrm) {
handlePromoSignup();
const handleConfirmPromoCode = (code) => {
if (code === marchEmailPrm) {
handlePromoSignup(code);
}
};
@@ -467,7 +467,7 @@ const SignupPage = () => {
}
};
const handlePromoSignup = async () => {
const handlePromoSignup = async (code) => {
let dataValues = validateUserData();
if (dataValues === null) {
return;
@@ -540,7 +540,8 @@ const SignupPage = () => {
<h1 className="signup-header-text">Create A Novodraft Account</h1>
{!isUpgrade ? (
<h5 className="signup-header-text trial">
Start a free trial - save hours and draft smarter discovery
Create an account to start a free trial - save time and draft
smarter
</h5>
) : (
<></>

View File

@@ -2,7 +2,13 @@ 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";
import {
collection,
getDocs,
query,
where,
onSnapshot,
} from "firebase/firestore";
const initialState = {
group: undefined,
@@ -16,9 +22,9 @@ const AppContext = createContext({
const AppProvider = ({ children }) => {
const { currentUserEmail } = useContext(AuthContext);
const [appState, appDispatch] = useReducer(useGroupReducer, initialState);
console.log("currentUserEmail in AppProvider", currentUserEmail);
useEffect(() => {
if (typeof currentUserEmail === 'undefined') {
if (typeof currentUserEmail === "undefined") {
return;
}
if (!currentUserEmail) {
@@ -26,13 +32,10 @@ const AppProvider = ({ children }) => {
return;
}
return onSnapshot(
query(
collection(db, "users"),
where("email", "==", currentUserEmail)
),
snapshot => {
query(collection(db, "users"), where("email", "==", currentUserEmail)),
(snapshot) => {
const doc = snapshot.docs[0];
if (typeof doc === 'undefined') {
if (typeof doc === "undefined") {
appDispatch({ type: "DELETE_GROUP" });
} else {
appDispatch({ type: "CREATE_GROUP", payload: doc.data() });

View File

@@ -8,6 +8,40 @@
margin: 30px 0px 0px 0px;
}
.dash-promo-first-box {
margin-top: 8px;
padding: 8px 48px;
}
.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-promo-first-text {
font-size: 0.94rem;
letter-spacing: 0.03rem;
font-family: Roboto;
font-weight: 300;
margin-bottom: 2px;
margin-left: 16px;
width: 680px;
color: #3b3b3b;
}
.dash-heading-text {
font-size: 1.3rem;
}
@@ -86,6 +120,11 @@
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 {

View File

@@ -131,8 +131,8 @@ section.section-1 {
font-family: Roboto;
color: #fff;
padding: 20px 0px 10px 0px;
font-size: 20px;
font-weight: 100;
font-size: 19px;
font-weight: 300;
max-width: 370px;
line-height: 29px;
text-align: justify;
@@ -163,10 +163,10 @@ h1.heading-2 {
p.text-block-1 {
color: #fff;
font-family: Roboto;
font-size: 25px;
font-size: 26px;
font-style: normal;
font-weight: 300;
line-height: 1.6;
line-height: 1.5;
width: 525px;
margin-top: 24px;
}
@@ -325,7 +325,7 @@ p.card-texts {
font-family: Roboto;
font-size: 1.7rem;
font-weight: 400;
margin-top: 8px;
margin-top: 2px;
letter-spacing: -0.007rem;
color: #1f74a7;
}
@@ -362,7 +362,7 @@ p.card-title {
}
.home-card-text-box {
padding: 8px 10px 0px 10px;
padding: 2px 10px 0px 10px;
width: 270px;
height: 150px;
}

View File

@@ -57,6 +57,7 @@
flex-direction: row;
width: 167px;
margin-top: -2px;
cursor: pointer;
}
.novo-logo {