done
This commit is contained in:
parent
02c8197678
commit
cf9c4d44cf
|
@ -119,9 +119,9 @@ export const updatePropertyById = async (req, res) => {
|
||||||
// Function to update fundDetails
|
// Function to update fundDetails
|
||||||
export const addFundDetails = async (req, res) => {
|
export const addFundDetails = async (req, res) => {
|
||||||
const { id } = req.params; // This should be the propertyId
|
const { id } = req.params; // This should be the propertyId
|
||||||
const { fundValue, fundPercentage } = req.body;
|
const { fundValue, fundPercentage, userId } = req.body;
|
||||||
|
|
||||||
console.log("d", id, { fundValue, fundPercentage });
|
console.log("d", id, { fundValue, fundPercentage, userId });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Change findById to findOne with the correct field name
|
// Change findById to findOne with the correct field name
|
||||||
|
@ -135,6 +135,7 @@ export const addFundDetails = async (req, res) => {
|
||||||
const newFundDetail = {
|
const newFundDetail = {
|
||||||
fundValue,
|
fundValue,
|
||||||
fundPercentage,
|
fundPercentage,
|
||||||
|
userId
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add new fund detail to fundDetails array
|
// Add new fund detail to fundDetails array
|
||||||
|
@ -151,6 +152,49 @@ export const addFundDetails = async (req, res) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const deleteFundDetail = async (req, res) => {
|
||||||
|
const { id, fundDetailId } = req.params;
|
||||||
|
const userId = req.userId;
|
||||||
|
|
||||||
|
console.log("Attempting to delete Fund Detail...");
|
||||||
|
console.log("Property ID:", id);
|
||||||
|
console.log("Fund Detail ID:", fundDetailId);
|
||||||
|
console.log("User ID from token:", userId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const property = await PropertyModal.findOne({ propertyId: id });
|
||||||
|
if (!property) {
|
||||||
|
return res.status(404).json({ message: 'Property not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Fund Details:", property.fundDetails);
|
||||||
|
|
||||||
|
const fundDetailIndex = property.fundDetails.findIndex(
|
||||||
|
(detail) => detail._id.toString() === fundDetailId && detail.userId.toString() === userId
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Fund Detail Index:", fundDetailIndex);
|
||||||
|
if (fundDetailIndex === -1) {
|
||||||
|
return res.status(403).json({ message: 'Not authorized to delete this fund detail' });
|
||||||
|
}
|
||||||
|
|
||||||
|
property.fundDetails.splice(fundDetailIndex, 1);
|
||||||
|
await property.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: 'Fund detail deleted', property });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting fund detail:", error);
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Getting funds from the DB
|
// Getting funds from the DB
|
||||||
export const getFundDetails = async (req, res) => {
|
export const getFundDetails = async (req, res) => {
|
||||||
const { id } = req.params; // Property ID
|
const { id } = req.params; // Property ID
|
||||||
|
@ -172,13 +216,6 @@ export const getFundDetails = async (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export const getProperties = async (req, res) => {
|
// export const getProperties = async (req, res) => {
|
||||||
// try {
|
// try {
|
||||||
// const properties = await PropertyModal.find(); // Fetch all properties from MongoDB
|
// const properties = await PropertyModal.find(); // Fetch all properties from MongoDB
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import UserModel from "../models/user.js"
|
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const secret = process.env.SECRET_KEY
|
const secret = process.env.SECRET_KEY;
|
||||||
|
|
||||||
const auth = async (req, res, next) => {
|
const auth = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
// Check if the authorization header exists
|
||||||
// Check if the authorization header exists
|
if (!req.headers.authorization) {
|
||||||
if (!req.headers.authorization) {
|
|
||||||
return res.status(401).json({ message: "Authorization header missing" });
|
return res.status(401).json({ message: "Authorization header missing" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = req.headers.authorization.split(" ")[1];
|
const token = req.headers.authorization.split(" ")[1]; // Extract the token from 'Bearer <token>'
|
||||||
const isCustomAuth = token.length < 500;
|
const isCustomAuth = token.length < 500; // Check if it's a custom JWT or a third-party auth
|
||||||
|
|
||||||
let decodedData;
|
let decodedData;
|
||||||
|
|
||||||
if (token && isCustomAuth) {
|
if (token && isCustomAuth) {
|
||||||
decodedData = jwt.verify(token, secret);
|
decodedData = jwt.verify(token, secret); // Verify the custom JWT
|
||||||
req.userId = decodedData?.id;
|
req.userId = decodedData?.id; // Set the user ID to request object
|
||||||
} else {
|
} else {
|
||||||
decodedData = jwt.decode(token);
|
decodedData = jwt.decode(token); // Decode third-party tokens (e.g., Google OAuth)
|
||||||
// const googleId = decodedData?.sub.toString();
|
req.userId = decodedData?.sub; // Usually for third-party tokens, user ID is in `sub`
|
||||||
// const user = await UserModel.findOne({ googleId });
|
|
||||||
// req.userId = user?._id;
|
|
||||||
req.userId = decodedData?.id;
|
|
||||||
}
|
}
|
||||||
next();
|
|
||||||
|
next(); // Continue to the next middleware or route handler
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
res.status(403).json({ message: "Authentication failed" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -306,13 +306,16 @@ const propertySchema = mongoose.Schema({
|
||||||
fundDetails: [
|
fundDetails: [
|
||||||
{
|
{
|
||||||
fundValue: {
|
fundValue: {
|
||||||
type: Number, // Adjust type if needed (String or Number)
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
fundPercentage: {
|
fundPercentage: {
|
||||||
type: Number, // Adjust type if needed
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
userId:{
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
getProperties,
|
getProperties,
|
||||||
addFundDetails,
|
addFundDetails,
|
||||||
getFundDetails,
|
getFundDetails,
|
||||||
|
deleteFundDetail,
|
||||||
} from "../controllers/property.js";
|
} from "../controllers/property.js";
|
||||||
|
|
||||||
router.post("/", auth, createProperty);
|
router.post("/", auth, createProperty);
|
||||||
|
@ -18,5 +19,7 @@ router.put("/:id", updatePropertyById);
|
||||||
router.get("/", getProperties);
|
router.get("/", getProperties);
|
||||||
router.put("/:id/fund-details", addFundDetails);
|
router.put("/:id/fund-details", addFundDetails);
|
||||||
router.get("/:id/fund-details", getFundDetails);
|
router.get("/:id/fund-details", getFundDetails);
|
||||||
|
router.delete('/:id/fund-details/:fundDetailId', auth, deleteFundDetail);
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="module" crossorigin src="/assets/index-DjwKvwco.js"></script>
|
<script type="module" crossorigin src="/assets/index-CVyH-t6Z.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-iEl-il0E.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-iEl-il0E.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -123,19 +123,43 @@ const Dashboard = () => {
|
||||||
<span>
|
<span>
|
||||||
Welcome to{" "}
|
Welcome to{" "}
|
||||||
<span style={{ color: "#067ADC" }}>
|
<span style={{ color: "#067ADC" }}>
|
||||||
|
<NavLink
|
||||||
|
to={`/profile/${user.result.userId}`}
|
||||||
<NavLink
|
className="link-primary text-decoration-none"
|
||||||
to={`/profile/${user.result.userId}`}
|
target="_blank"
|
||||||
className="link-primary text-decoration-none"
|
>
|
||||||
target="_blank"
|
{user.result.title}. {user.result.firstName}{" "}
|
||||||
>
|
{user.result.middleName} {user.result.lastName}
|
||||||
{user.result.title}. {user.result.firstName}{" "}
|
</NavLink>
|
||||||
{user.result.middleName} {user.result.lastName}
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<br /> <br /> <br /> <br />
|
||||||
|
|
||||||
|
<div className="banner_taital">
|
||||||
|
<h1 style={{ color: "#fda417", fontSize: "30px",
|
||||||
|
padding: "10px",
|
||||||
|
fontWeight: "normal" }} className="services_taital">
|
||||||
|
Now you are accessing the world's only portal which has Streamlined the
|
||||||
|
<h1 style={{ fontSize: "30px",
|
||||||
|
padding: "10px",
|
||||||
|
fontWeight: "normal" }}> investor-borrower interactions, </h1>
|
||||||
|
|
||||||
|
</h1>
|
||||||
|
<h1 className="services_taital" style={{color: "#fda417",fontSize: "30px",
|
||||||
|
padding: "10px",
|
||||||
|
fontWeight: "normal" }} >
|
||||||
|
gaining complete visibility
|
||||||
|
into your data, and using smart filters to
|
||||||
|
<h1 className="services_taital" style={{fontSize: "30px",
|
||||||
|
padding: "10px",
|
||||||
|
fontWeight: "normal" }} >create automatic
|
||||||
|
workflows{" "}</h1>
|
||||||
|
|
||||||
|
</h1>
|
||||||
|
<br /> <br /> <br /> <br /> <br />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
{/* Dynamic content based on the selected tab */}
|
{/* Dynamic content based on the selected tab */}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import Footer from "./Footer";
|
||||||
import { Modal, Button, Form } from "react-bootstrap"; // Importing Modal components
|
import { Modal, Button, Form } from "react-bootstrap"; // Importing Modal components
|
||||||
import { addFundDetails } from "../redux/features/propertySlice";
|
import { addFundDetails } from "../redux/features/propertySlice";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { fetchFundDetails} from "../redux/features/fundSlice";
|
import { deleteFundDetail } from "../redux/features/propertySlice";
|
||||||
|
|
||||||
const PropertyView = () => {
|
const PropertyView = () => {
|
||||||
const { id } = useParams(); // Extract the property ID from the route
|
const { id } = useParams(); // Extract the property ID from the route
|
||||||
|
@ -17,20 +17,7 @@ const PropertyView = () => {
|
||||||
(state) => state.property
|
(state) => state.property
|
||||||
);
|
);
|
||||||
const { user } = useSelector((state) => ({ ...state.auth }));
|
const { user } = useSelector((state) => ({ ...state.auth }));
|
||||||
|
// console.log("usr", user.result.userId);
|
||||||
// This also works !!
|
|
||||||
// const fundDetails = useSelector(selectFundDetails);
|
|
||||||
// console.log("fundDetailsa", fundDetails)
|
|
||||||
|
|
||||||
const {fundDetails} = useSelector((state) => state.fundDetails);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchFundDetails(id));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState("propertydetails");
|
const [activeTab, setActiveTab] = useState("propertydetails");
|
||||||
|
|
||||||
|
@ -48,10 +35,6 @@ const PropertyView = () => {
|
||||||
}
|
}
|
||||||
}, [id, dispatch]);
|
}, [id, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchFundDetails(id));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === "succeeded") {
|
if (status === "succeeded") {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
@ -89,15 +72,30 @@ const PropertyView = () => {
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (formData.fundValue <= 0 || formData.fundPercentage <= 0) {
|
||||||
|
return toast.error("Please enter valid funding details");
|
||||||
|
}
|
||||||
|
|
||||||
const fundDetails = {
|
const fundDetails = {
|
||||||
fundValue: Number(formData.fundValue), // Ensure number conversion if needed
|
fundValue: Number(formData.fundValue), // Ensure number conversion if needed
|
||||||
fundPercentage: Number(formData.fundPercentage),
|
fundPercentage: Number(formData.fundPercentage),
|
||||||
|
userId: user.result._id,
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(addFundDetails({ id, fundDetails, toast }));
|
dispatch(addFundDetails({ id, fundDetails, toast }));
|
||||||
handleCloseModal();
|
handleCloseModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const handleDelete = (fundDetailId) => {
|
||||||
|
// dispatch(deleteFundDetail({ id, fundDetailId }));
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleDelete = (fundDetailId) => {
|
||||||
|
const token = JSON.parse(localStorage.getItem("profile")).token;
|
||||||
|
dispatch(deleteFundDetail({ id, fundDetailId, token }));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
@ -200,7 +198,7 @@ const PropertyView = () => {
|
||||||
className="fa fa-dollar"
|
className="fa fa-dollar"
|
||||||
style={{ color: "#F74B02" }}
|
style={{ color: "#F74B02" }}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
{selectedProperty.totalCoststoBuyAtoB}
|
{selectedProperty?.totalCoststoBuyAtoB || "N/A"}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
|
@ -219,7 +217,8 @@ const PropertyView = () => {
|
||||||
className="fa fa-dollar"
|
className="fa fa-dollar"
|
||||||
style={{ color: "#F74B02" }}
|
style={{ color: "#F74B02" }}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
{selectedProperty.NetProfit}
|
{/* {selectedProperty.NetProfit} */}
|
||||||
|
{selectedProperty?.NetProfit || "N/A"}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -389,44 +388,35 @@ const PropertyView = () => {
|
||||||
<div>
|
<div>
|
||||||
{activeTab === "Funding Details" && (
|
{activeTab === "Funding Details" && (
|
||||||
<div className="tab-pane active">
|
<div className="tab-pane active">
|
||||||
|
<div>
|
||||||
<div>
|
{selectedProperty.fundDetails.length === 0 ? (
|
||||||
{selectedProperty.fundDetails.length === 0 ? (
|
<p>No fund details available.</p>
|
||||||
<p>No fund details available.</p>
|
) : (
|
||||||
) : (
|
<ul>
|
||||||
<ul>
|
{selectedProperty.fundDetails.map((detail, index) => (
|
||||||
{selectedProperty.fundDetails.map((detail, index) => (
|
<li key={index}>
|
||||||
<li key={index}>
|
Fund Value: {detail.fundValue}, Fund Percentage:{" "}
|
||||||
Fund Value: {detail.fundValue}, Fund Percentage: {detail.fundPercentage}%
|
{detail.fundPercentage}%
|
||||||
</li>
|
{user?.result?.userId ? (
|
||||||
))}
|
<div>
|
||||||
</ul>
|
{user?.result?._id === detail.userId && (
|
||||||
)}
|
<button
|
||||||
|
onClick={() => handleDelete(detail._id)}
|
||||||
|
>
|
||||||
{fundDetails.length === 0 ? (
|
Delete
|
||||||
<p>No fund details available.</p>
|
</button>
|
||||||
) : (
|
)}
|
||||||
<ul>
|
</div>
|
||||||
{fundDetails.map((detail, index) => (
|
) : (
|
||||||
<li key={index}>
|
<div></div>
|
||||||
Fund Value: {detail.fundValue}, Fund Percentage: {detail.fundPercentage}%
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -435,7 +425,6 @@ const PropertyView = () => {
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
{/* Modal for Investment/Funding */}
|
{/* Modal for Investment/Funding */}
|
||||||
{/* Modal for Investment/Funding */}
|
|
||||||
<Modal show={showModal} onHide={handleCloseModal}>
|
<Modal show={showModal} onHide={handleCloseModal}>
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title>Investment/Funding Details</Modal.Title>
|
<Modal.Title>Investment/Funding Details</Modal.Title>
|
||||||
|
|
|
@ -0,0 +1,458 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { fetchPropertyById } from "../redux/features/propertySlice";
|
||||||
|
import propertydummy from "../img/propertydummy.jpg";
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
import Footer from "./Footer";
|
||||||
|
import { Modal, Button, Form } from "react-bootstrap"; // Importing Modal components
|
||||||
|
import { addFundDetails } from "../redux/features/propertySlice";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { fetchFundDetails } from "../redux/features/fundSlice";
|
||||||
|
|
||||||
|
const PropertyView = () => {
|
||||||
|
const { id } = useParams(); // Extract the property ID from the route
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { selectedProperty, status, error } = useSelector(
|
||||||
|
(state) => state.property
|
||||||
|
);
|
||||||
|
const { user } = useSelector((state) => ({ ...state.auth }));
|
||||||
|
|
||||||
|
// This also works !!
|
||||||
|
// const fundDetails = useSelector(selectFundDetails);
|
||||||
|
// console.log("fundDetailsa", fundDetails)
|
||||||
|
|
||||||
|
const { fundDetails } = useSelector((state) => state.fundDetails);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchFundDetails(id));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState("propertydetails");
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true); // Loader state
|
||||||
|
const [showModal, setShowModal] = useState(false); // Modal state
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
fundValue: "0",
|
||||||
|
fundPercentage: "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
dispatch(fetchPropertyById(id));
|
||||||
|
}
|
||||||
|
}, [id, dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === "succeeded") {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
// if (status === "loading") {
|
||||||
|
// return <p>Loading property details...</p>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (status === "failed") {
|
||||||
|
return <p>Error loading property: {error}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle conditions for the "Willing to Invest/Fund" button and messages
|
||||||
|
const isOwner = user?.result?.userId === selectedProperty?.userId;
|
||||||
|
const isLoggedIn = !!user;
|
||||||
|
|
||||||
|
const handleShowModal = () => {
|
||||||
|
setShowModal(true); // Show the modal
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setShowModal(false); // Close the modal
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[name]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const fundDetails = {
|
||||||
|
fundValue: Number(formData.fundValue), // Ensure number conversion if needed
|
||||||
|
fundPercentage: Number(formData.fundPercentage),
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch(addFundDetails({ id, fundDetails, toast }));
|
||||||
|
handleCloseModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<br /> <br /> <br /> <br />
|
||||||
|
<br /> <br />
|
||||||
|
<div className="container tabs-wrap col-12">
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
<ul className="nav nav-tabs" role="tablist">
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
className={activeTab === "propertydetails" ? "active tab" : "tab"}
|
||||||
|
>
|
||||||
|
<a onClick={() => setActiveTab("propertydetails")} role="tab">
|
||||||
|
Property Details
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
className={activeTab === "Funding Details" ? "active tab" : "tab"}
|
||||||
|
>
|
||||||
|
<a onClick={() => setActiveTab("Funding Details")} role="tab">
|
||||||
|
Funding Details
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
className={activeTab === "Accounting" ? "active tab" : "tab"}
|
||||||
|
>
|
||||||
|
<a onClick={() => setActiveTab("Accounting")} role="tab">
|
||||||
|
Accounting
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="tab-content">
|
||||||
|
{activeTab === "propertydetails" && (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
className="card container tab-pane active col-12"
|
||||||
|
>
|
||||||
|
<div className="container col-12">
|
||||||
|
{loading ? (
|
||||||
|
<div className="loader">Loading...</div> // Loader
|
||||||
|
) : selectedProperty ? (
|
||||||
|
<div className="py-3 py-md-5 bg-light">
|
||||||
|
<div className="card-header bg-white">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-5 mt-3">
|
||||||
|
<div>
|
||||||
|
{selectedProperty.images &&
|
||||||
|
selectedProperty.images[0] ? (
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
selectedProperty.images[0].file ||
|
||||||
|
propertydummy
|
||||||
|
}
|
||||||
|
alt="Property Thumbnail"
|
||||||
|
style={{
|
||||||
|
marginTop: "0px",
|
||||||
|
maxWidth: "400px",
|
||||||
|
maxHeight: "400px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={propertydummy}
|
||||||
|
alt="Default Property Thumbnail"
|
||||||
|
style={{
|
||||||
|
marginTop: "0px",
|
||||||
|
maxWidth: "400px",
|
||||||
|
maxHeight: "400px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <img
|
||||||
|
src={selectedProperty.images?.[0]?.file || propertydummy}
|
||||||
|
alt="Property Thumbnail"
|
||||||
|
style={{ marginTop: "0px", maxWidth: "400px", maxHeight: "400px" }}
|
||||||
|
loading="lazy"
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div className="label-stock border">
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
color: "#fda417",
|
||||||
|
fontSize: "30px",
|
||||||
|
padding: "10px",
|
||||||
|
fontWeight: "normal",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Funding Required:{" "}
|
||||||
|
<span
|
||||||
|
style={{ color: "#000000", fontSize: "25px" }}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="fa fa-dollar"
|
||||||
|
style={{ color: "#F74B02" }}
|
||||||
|
/>{" "}
|
||||||
|
{selectedProperty.totalCoststoBuyAtoB}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
color: "#fda417",
|
||||||
|
fontSize: "30px",
|
||||||
|
padding: "10px",
|
||||||
|
fontWeight: "normal",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Net profit:{" "}
|
||||||
|
<span
|
||||||
|
style={{ color: "#000000", fontSize: "25px" }}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="fa fa-dollar"
|
||||||
|
style={{ color: "#F74B02" }}
|
||||||
|
/>{" "}
|
||||||
|
{selectedProperty.NetProfit}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
{/* "Willing to Invest/Fund" Button and Conditional Messages */}
|
||||||
|
<button
|
||||||
|
className="btn btn-primary back"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#fda417",
|
||||||
|
border: "#fda417",
|
||||||
|
}}
|
||||||
|
disabled={isOwner || !isLoggedIn}
|
||||||
|
onClick={
|
||||||
|
isOwner || !isLoggedIn ? null : handleShowModal
|
||||||
|
} // Show modal only if not owner or logged in
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: "25px",
|
||||||
|
fontWeight: "normal",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Willing to Invest/Fund
|
||||||
|
</span>{" "}
|
||||||
|
</button>
|
||||||
|
{isOwner && (
|
||||||
|
<span style={{ color: "red", marginTop: "10px" }}>
|
||||||
|
You cannot invest in your own property.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{!isLoggedIn && (
|
||||||
|
<span style={{ color: "red", marginTop: "10px" }}>
|
||||||
|
Please login to invest.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-7 mt-3">
|
||||||
|
<div className="product-view">
|
||||||
|
<h4
|
||||||
|
className="product-name"
|
||||||
|
style={{ color: "#fda417", fontSize: "25px" }}
|
||||||
|
>
|
||||||
|
{selectedProperty.address}
|
||||||
|
<label className="label-stock bg-success">
|
||||||
|
Verified Property
|
||||||
|
</label>
|
||||||
|
</h4>
|
||||||
|
<hr />
|
||||||
|
<p className="product-path">
|
||||||
|
<span
|
||||||
|
style={{ color: "#fda417", fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
City:{" "}
|
||||||
|
</span>{" "}
|
||||||
|
{selectedProperty.city}
|
||||||
|
{" "} /{" "}
|
||||||
|
{" "}
|
||||||
|
{" "}
|
||||||
|
<span
|
||||||
|
style={{ color: "#fda417", fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
County:{" "}
|
||||||
|
</span>{" "}
|
||||||
|
{selectedProperty.county} {" "}/{" "}
|
||||||
|
{" "}
|
||||||
|
<span
|
||||||
|
style={{ color: "#fda417", fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
State:{" "}
|
||||||
|
</span>{" "}
|
||||||
|
{selectedProperty.state} {" "}/ {" "}
|
||||||
|
{" "}
|
||||||
|
<span
|
||||||
|
style={{ color: "#fda417", fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
Zipcode:{" "}
|
||||||
|
</span>{" "}
|
||||||
|
{selectedProperty.zip}
|
||||||
|
{" "}
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
className="selling-price"
|
||||||
|
style={{ color: "#fda417", fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
Total Living Square Foot:{" "}
|
||||||
|
</span>
|
||||||
|
{selectedProperty.totallivingsqft}
|
||||||
|
<p></p>
|
||||||
|
<span
|
||||||
|
className=""
|
||||||
|
style={{ color: "#fda417", fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
Cost per Square Foot:{" "}
|
||||||
|
</span>
|
||||||
|
${selectedProperty.costpersqft}/sqft
|
||||||
|
<p></p>
|
||||||
|
<span
|
||||||
|
className=""
|
||||||
|
style={{ color: "#fda417", fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
Year Built:{" "}
|
||||||
|
</span>
|
||||||
|
{selectedProperty.yearBuild}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 card bg-white label-stock border">
|
||||||
|
<h5
|
||||||
|
className="mb-0"
|
||||||
|
style={{ color: "#fda417", fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
Legal Description
|
||||||
|
</h5>
|
||||||
|
<span>
|
||||||
|
{selectedProperty.legal
|
||||||
|
? selectedProperty.legal
|
||||||
|
: "No data available"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-12 mt-3">
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header bg-white">
|
||||||
|
<h4
|
||||||
|
className="product-name"
|
||||||
|
style={{ color: "#fda417", fontSize: "25px" }}
|
||||||
|
>
|
||||||
|
Description
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<p>
|
||||||
|
Lorem Ipsum is simply dummy text of the printing
|
||||||
|
and typesetting industry. Lorem Ipsum has been
|
||||||
|
the industry's standard dummy text ever since
|
||||||
|
the 1500s, when an unknown printer took a galley
|
||||||
|
of type and scrambled it to make a type specimen
|
||||||
|
book. It has survived not only five centuries,
|
||||||
|
but also the leap into electronic typesetting,
|
||||||
|
remaining essentially unchanged. It was
|
||||||
|
popularised in the 1960s with the release of
|
||||||
|
Letraset sheets containing Lorem Ipsum passages,
|
||||||
|
and more recently with desktop publishing
|
||||||
|
software like Aldus PageMaker including versions
|
||||||
|
of Lorem Ipsum.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>No property found.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "Funding Details" && (
|
||||||
|
<div>
|
||||||
|
{activeTab === "Funding Details" && (
|
||||||
|
<div className="tab-pane active">
|
||||||
|
<div>
|
||||||
|
{selectedProperty.fundDetails.length === 0 ? (
|
||||||
|
<p>No fund details available.</p>
|
||||||
|
) : (
|
||||||
|
<ul>
|
||||||
|
{selectedProperty.fundDetails.map((detail, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
Fund Value: {detail.fundValue}, Fund Percentage:{" "}
|
||||||
|
{detail.fundPercentage}%
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{fundDetails.length === 0 ? (
|
||||||
|
<p>No fund details available.</p>
|
||||||
|
) : (
|
||||||
|
<ul>
|
||||||
|
{fundDetails.map((detail, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
Fund Value: {detail.fundValue}, Fund Percentage:{" "}
|
||||||
|
{detail.fundPercentage}%
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "Accounting" && <div></div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
{/* Modal for Investment/Funding */}
|
||||||
|
{/* Modal for Investment/Funding */}
|
||||||
|
<Modal show={showModal} onHide={handleCloseModal}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>Investment/Funding Details</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Fund Value</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="number"
|
||||||
|
id="fundValue"
|
||||||
|
name="fundValue" // Added the name attribute
|
||||||
|
value={formData.fundValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Fund Percentage</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="number"
|
||||||
|
id="fundPercentage"
|
||||||
|
name="fundPercentage" // Added the name attribute
|
||||||
|
value={formData.fundPercentage}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PropertyView;
|
|
@ -58,7 +58,6 @@ export const updateProperty = createAsyncThunk(
|
||||||
try {
|
try {
|
||||||
const response = await api.updateProperty(id, propertyData);
|
const response = await api.updateProperty(id, propertyData);
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return rejectWithValue(error.response.data);
|
return rejectWithValue(error.response.data);
|
||||||
}
|
}
|
||||||
|
@ -66,13 +65,16 @@ export const updateProperty = createAsyncThunk(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const addFundDetails = createAsyncThunk(
|
export const addFundDetails = createAsyncThunk(
|
||||||
'property/addFundDetails',
|
"property/addFundDetails",
|
||||||
async ({ id, fundDetails, toast }, { rejectWithValue }) => {
|
async ({ id, fundDetails, toast }, { rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.put(
|
const response = await axios.put(
|
||||||
`${import.meta.env.VITE_REACT_APP_SECRET}/properties/${id}/fund-details`,
|
`${
|
||||||
fundDetails);
|
import.meta.env.VITE_REACT_APP_SECRET
|
||||||
toast.success("Submitted Successfully");
|
}/properties/${id}/fund-details`,
|
||||||
|
fundDetails
|
||||||
|
);
|
||||||
|
toast.success("Submitted Successfully");
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return rejectWithValue(error.response.data);
|
return rejectWithValue(error.response.data);
|
||||||
|
@ -80,6 +82,30 @@ export const addFundDetails = createAsyncThunk(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const deleteFundDetail = createAsyncThunk(
|
||||||
|
"property/deleteFundDetail",
|
||||||
|
async ({ id, fundDetailId, token }, { rejectWithValue }) => {
|
||||||
|
// console.log("Token received:", token, fundDetailId, id);
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(
|
||||||
|
`http://localhost:3002/properties/${id}/fund-details/${fundDetailId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`, // Use the token passed in as a parameter
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return rejectWithValue(error.response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export const getProperties = createAsyncThunk("property/getProperties", async () => {
|
// export const getProperties = createAsyncThunk("property/getProperties", async () => {
|
||||||
// const response = await axios.get(`${import.meta.env.VITE_REACT_APP_SECRET}/properties`); // Backend endpoint
|
// const response = await axios.get(`${import.meta.env.VITE_REACT_APP_SECRET}/properties`); // Backend endpoint
|
||||||
|
@ -95,14 +121,17 @@ export const addFundDetails = createAsyncThunk(
|
||||||
// );
|
// );
|
||||||
|
|
||||||
export const getProperties = createAsyncThunk(
|
export const getProperties = createAsyncThunk(
|
||||||
'properties/getProperties',
|
"properties/getProperties",
|
||||||
async ({ page, limit, keyword = "" }) => {
|
async ({ page, limit, keyword = "" }) => {
|
||||||
const response = await axios.get(`${import.meta.env.VITE_REACT_APP_SECRET}/properties?page=${page}&limit=${limit}&keyword=${keyword}`);
|
const response = await axios.get(
|
||||||
|
`${
|
||||||
|
import.meta.env.VITE_REACT_APP_SECRET
|
||||||
|
}/properties?page=${page}&limit=${limit}&keyword=${keyword}`
|
||||||
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const propertySlice = createSlice({
|
const propertySlice = createSlice({
|
||||||
name: "property",
|
name: "property",
|
||||||
initialState: {
|
initialState: {
|
||||||
|
@ -115,6 +144,7 @@ const propertySlice = createSlice({
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
properties: [],
|
properties: [],
|
||||||
|
fundDetails: [],
|
||||||
},
|
},
|
||||||
reducers: {},
|
reducers: {},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
|
@ -191,6 +221,21 @@ const propertySlice = createSlice({
|
||||||
.addCase(addFundDetails.rejected, (state, action) => {
|
.addCase(addFundDetails.rejected, (state, action) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.error = action.payload;
|
state.error = action.payload;
|
||||||
|
})
|
||||||
|
|
||||||
|
.addCase(deleteFundDetail.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
})
|
||||||
|
.addCase(deleteFundDetail.fulfilled, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
// Remove the deleted fund detail from state
|
||||||
|
state.fundDetails = state.fundDetails.filter(
|
||||||
|
(detail) => detail._id !== action.meta.arg.id
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.addCase(deleteFundDetail.rejected, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.error = action.payload;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue