diff --git a/ditch-the-agent/src/App.tsx b/ditch-the-agent/src/App.tsx
index ab4eba1..5ab3fa2 100644
--- a/ditch-the-agent/src/App.tsx
+++ b/ditch-the-agent/src/App.tsx
@@ -1,5 +1,11 @@
import { Outlet } from 'react-router-dom';
+import Tracker from './components/Tracker';
-const App = () => ;
+const App = () => (
+ <>
+
+
+ >
+);
export default App;
diff --git a/ditch-the-agent/src/components/Tracker.tsx b/ditch-the-agent/src/components/Tracker.tsx
new file mode 100644
index 0000000..c9c112f
--- /dev/null
+++ b/ditch-the-agent/src/components/Tracker.tsx
@@ -0,0 +1,31 @@
+import React, { useEffect } from 'react';
+
+const Tracker: React.FC = () => {
+ useEffect(() => {
+ let websiteId = '';
+
+ if (import.meta.env.MODE === 'production') {
+ websiteId = 'cmhannsc96cerxj0euz76p49w';
+ } else if (import.meta.env.MODE === 'beta') {
+ websiteId = 'cmhanoe4f6cexxj0e3yxb5gq1';
+ }
+
+ if (websiteId) {
+ const script = document.createElement('script');
+ script.src = 'https://tianji.aimloperations.com/tracker.js';
+ script.async = true;
+ script.defer = true;
+ script.setAttribute('data-website-id', websiteId);
+
+ document.body.appendChild(script);
+
+ return () => {
+ document.body.removeChild(script);
+ };
+ }
+ }, []);
+
+ return null;
+};
+
+export default Tracker;
diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Documents/Dialog/LenderFinancingAgreementDialogContent.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Documents/Dialog/LenderFinancingAgreementDialogContent.tsx
new file mode 100644
index 0000000..021cc3a
--- /dev/null
+++ b/ditch-the-agent/src/components/sections/dashboard/Home/Documents/Dialog/LenderFinancingAgreementDialogContent.tsx
@@ -0,0 +1,284 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Box,
+ Button,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ TextField,
+ FormControlLabel,
+ Checkbox,
+ CircularProgress,
+ Alert,
+ InputAdornment,
+ Grid,
+ Paper,
+ Typography,
+} from '@mui/material';
+import { PropertiesAPI, OfferAPI } from 'types';
+import { axiosInstance } from '../../../../../../axiosApi';
+
+interface LenderFinancingAgreementDialogContentProps {
+ closeDialog: () => void;
+ properties: PropertiesAPI[];
+}
+
+const LenderFinancingAgreementDialogContent: React.FC = ({
+ closeDialog,
+ properties,
+}) => {
+ const [selectedPropertyId, setSelectedPropertyId] = useState('');
+ const [offers, setOffers] = useState([]);
+ const [selectedOfferId, setSelectedOfferId] = useState('');
+ const [loanType, setLoanType] = useState('30_year_fixed');
+ const [interestRate, setInterestRate] = useState('');
+ const [pmi, setPmi] = useState(false);
+ const [offerPrice, setOfferPrice] = useState('');
+ const [closingDate, setClosingDate] = useState('');
+ const [loadingOffers, setLoadingOffers] = useState(false);
+ const [submitting, setSubmitting] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (selectedPropertyId) {
+ fetchOffers(selectedPropertyId as number);
+ } else {
+ setOffers([]);
+ setSelectedOfferId('');
+ }
+ }, [selectedPropertyId]);
+
+ const fetchOffers = async (propertyId: number) => {
+ setLoadingOffers(true);
+ try {
+ const response = await axiosInstance.get(`/offers/?property=${propertyId}`);
+ setOffers(response.data);
+ } catch (err) {
+ console.error('Failed to fetch offers', err);
+ } finally {
+ setLoadingOffers(false);
+ }
+ };
+
+ const handleSubmit = async () => {
+ if (!selectedPropertyId || !selectedOfferId || !interestRate || !offerPrice || !closingDate) {
+ setError('Please fill in all required fields.');
+ return;
+ }
+
+ setSubmitting(true);
+ setError(null);
+
+ const selectedProperty = properties.find((p) => p.id === selectedPropertyId);
+
+ const payload = {
+ property: selectedPropertyId,
+ document_type: 'lender_financing_agreement',
+ sub_document: {
+ loan_type: loanType,
+ interest_rate: parseFloat(interestRate),
+ pmi: pmi,
+ offer_price: parseFloat(offerPrice),
+ closing_date: closingDate,
+ property_address: selectedProperty?.address || '',
+ property_owner:
+ selectedProperty?.owner?.user?.first_name + ' ' + selectedProperty?.owner?.user?.last_name ||
+ '',
+ },
+ };
+
+ try {
+ await axiosInstance.post('/document/', payload);
+ closeDialog();
+ window.location.reload();
+ } catch (err) {
+ console.error('Failed to create document', err);
+ setError('Failed to create document. Please try again.');
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ const generateAgreementText = () => {
+ const selectedProperty = properties.find((p) => p.id === selectedPropertyId);
+ const selectedOffer = offers.find((o) => o.id === selectedOfferId);
+ const borrowerName = selectedOffer
+ ? `${selectedOffer.user.first_name} ${selectedOffer.user.last_name}`
+ : '[Borrower Name]';
+ const propertyAddress = selectedProperty ? selectedProperty.address : '[Property Address]';
+ const currentDate = new Date().toLocaleDateString();
+
+ return `LENDER FINANCING AGREEMENT
+
+Date: ${currentDate}
+
+Property Address: ${propertyAddress}
+
+Borrower: ${borrowerName}
+Lender: [Your Lending Institution]
+
+Loan Details:
+- Loan Type: ${loanType.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())}
+- Interest Rate: ${interestRate || '[Rate]'}%
+- Purchase Price: $${offerPrice || '[Price]'}
+- Closing Date: ${closingDate || '[Date]'}
+- PMI Included: ${pmi ? 'Yes' : 'No'}
+
+This agreement serves as a preliminary commitment to lend to the Borrower for the purchase of the property located at the address above, subject to the terms and conditions outlined herein.
+
+1. LOAN TERMS
+The Lender agrees to provide a loan to the Borrower in the amount necessary to purchase the property, less any down payment, under the loan program specified above.
+
+2. INTEREST RATE
+The interest rate specified is subject to market fluctuations until locked in by the Borrower and Lender.
+
+3. CONDITIONS
+This agreement is contingent upon:
+ a. Satisfactory appraisal of the property.
+ b. Verification of Borrower's income and assets.
+ c. Clear title to the property.
+
+4. CLOSING
+The loan is expected to close on or before the Closing Date specified above.
+
+By proceeding, the Lender acknowledges the intent to finance this transaction.`;
+ };
+
+ return (
+ <>
+ Create Lender Financing Agreement
+
+
+
+
+ {error && {error}}
+
+
+ Property
+
+
+
+
+ Offer
+
+ {loadingOffers && (
+
+ )}
+
+
+ setOfferPrice(e.target.value)}
+ InputProps={{
+ startAdornment: $,
+ }}
+ />
+
+ setClosingDate(e.target.value)}
+ InputLabelProps={{ shrink: true }}
+ />
+
+
+ Loan Type
+
+
+
+ setInterestRate(e.target.value)}
+ InputProps={{
+ endAdornment: %,
+ }}
+ />
+
+ setPmi(e.target.checked)} />}
+ label="PMI Included"
+ />
+
+
+
+
+
+ PREVIEW
+
+
+ {generateAgreementText()}
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default LenderFinancingAgreementDialogContent;
diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Documents/Dialog/VendorDocumentDialog.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Documents/Dialog/VendorDocumentDialog.tsx
index a4f7d09..989a877 100644
--- a/ditch-the-agent/src/components/sections/dashboard/Home/Documents/Dialog/VendorDocumentDialog.tsx
+++ b/ditch-the-agent/src/components/sections/dashboard/Home/Documents/Dialog/VendorDocumentDialog.tsx
@@ -1,13 +1,63 @@
-import { Dialog, Typography } from '@mui/material';
+import { Dialog } from '@mui/material';
import { DocumentDialogProps } from '../AddDocumentDialog';
import { ReactElement } from 'react';
+import LenderFinancingAgreementDialogContent from './LenderFinancingAgreementDialogContent';
+
+const VendorDocumentDialog = ({
+ showDialog,
+ closeDialog,
+ properties,
+}: DocumentDialogProps): ReactElement => {
+ // We need to check the vendor's business type.
+ // The account object is UserAPI, which doesn't have business_type directly.
+ // However, usually the vendor profile is fetched or attached.
+ // But wait, UserAPI doesn't have business_type. VendorAPI does.
+ // The parent passes 'account' which is UserAPI.
+ // We might need to fetch the vendor profile or assume it's available in context or passed down.
+ // In `Vendors.tsx`, we saw `VendorAPI` has `user` and `business_type`.
+ // In `AddDocumentDialog`, we just have `account` from `AccountContext`.
+ // Let's check `AccountContext` or `UserAPI` again.
+ // UserAPI: id, email, first_name, last_name, user_type...
+ // It doesn't have business_type.
+ // The user request implies the logged-in user is a Vendor.
+ // If I am a vendor, I should have a vendor profile.
+ // I might need to fetch it or check if it's attached.
+ // For now, I will assume I can get it or I need to fetch it.
+ // OR, maybe I can just check if the user is a vendor and then show a selection?
+ // But the requirement says "Vendor that is of type Mortgage Lendor".
+ // I'll assume for this task that I can fetch the vendor profile or it's available.
+ // Let's check if I can get the vendor profile.
+ // Actually, `DocumentManager` has `account`.
+ // Maybe I should fetch the vendor profile in `VendorDocumentDialog`?
+ // Or just show the option if they are a vendor, and maybe let them select?
+ // No, it should be specific.
+ // Let's try to fetch the vendor profile using the user ID.
+ // Or, simpler: Just check if I can find a way to know the business type.
+ // If not, I'll just show the dialog for now as a fallback or assume it's passed.
+ // Wait, `VendorAPI` has `user`.
+ // I'll try to fetch `/vendors/me/` or similar if it exists, or `/vendors/?user={id}`.
+ // Let's assume for now that if they are a vendor, we check their type.
+ // I'll add a check.
+
+ // For the purpose of this task and the "mock" nature of some parts,
+ // I will fetch the vendor profile in a useEffect.
+
+ // Wait, I can't easily use hooks inside the conditional return if I structure it badly.
+ // I'll rewrite the component to use state.
-const VendorDocumentDialog = ({ showDialog, closeDialog }: DocumentDialogProps): ReactElement => {
return (
-