This commit is contained in:
omkieit 2024-10-25 11:04:27 +05:30
parent 02c8197678
commit cf9c4d44cf
10 changed files with 683 additions and 125 deletions

View File

@ -119,9 +119,9 @@ export const updatePropertyById = async (req, res) => {
// Function to update fundDetails
export const addFundDetails = async (req, res) => {
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 {
// Change findById to findOne with the correct field name
@ -135,6 +135,7 @@ export const addFundDetails = async (req, res) => {
const newFundDetail = {
fundValue,
fundPercentage,
userId
};
// 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
export const getFundDetails = async (req, res) => {
const { id } = req.params; // Property ID
@ -172,13 +216,6 @@ export const getFundDetails = async (req, res) => {
// export const getProperties = async (req, res) => {
// try {
// const properties = await PropertyModal.find(); // Fetch all properties from MongoDB

View File

@ -1,35 +1,34 @@
import jwt from "jsonwebtoken";
import UserModel from "../models/user.js"
import dotenv from "dotenv";
dotenv.config();
const secret = process.env.SECRET_KEY
const secret = process.env.SECRET_KEY;
const auth = async (req, res, next) => {
try {
// Check if the authorization header exists
if (!req.headers.authorization) {
return res.status(401).json({ message: "Authorization header missing" });
}
const token = req.headers.authorization.split(" ")[1];
const isCustomAuth = token.length < 500;
const token = req.headers.authorization.split(" ")[1]; // Extract the token from 'Bearer <token>'
const isCustomAuth = token.length < 500; // Check if it's a custom JWT or a third-party auth
let decodedData;
if (token && isCustomAuth) {
decodedData = jwt.verify(token, secret);
req.userId = decodedData?.id;
decodedData = jwt.verify(token, secret); // Verify the custom JWT
req.userId = decodedData?.id; // Set the user ID to request object
} else {
decodedData = jwt.decode(token);
// const googleId = decodedData?.sub.toString();
// const user = await UserModel.findOne({ googleId });
// req.userId = user?._id;
req.userId = decodedData?.id;
decodedData = jwt.decode(token); // Decode third-party tokens (e.g., Google OAuth)
req.userId = decodedData?.sub; // Usually for third-party tokens, user ID is in `sub`
}
next();
next(); // Continue to the next middleware or route handler
} catch (error) {
console.log(error);
res.status(403).json({ message: "Authentication failed" });
}
};

View File

@ -306,13 +306,16 @@ const propertySchema = mongoose.Schema({
fundDetails: [
{
fundValue: {
type: Number, // Adjust type if needed (String or Number)
type: Number,
required: true,
},
fundPercentage: {
type: Number, // Adjust type if needed
type: Number,
required: true,
},
userId:{
type: String,
},
},
],
});

View File

@ -9,6 +9,7 @@ import {
getProperties,
addFundDetails,
getFundDetails,
deleteFundDetail,
} from "../controllers/property.js";
router.post("/", auth, createProperty);
@ -18,5 +19,7 @@ router.put("/:id", updatePropertyById);
router.get("/", getProperties);
router.put("/:id/fund-details", addFundDetails);
router.get("/:id/fund-details", getFundDetails);
router.delete('/:id/fund-details/:fundDetailId', auth, deleteFundDetail);
export default router;

File diff suppressed because one or more lines are too long

View File

@ -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">
</head>

View File

@ -123,8 +123,6 @@ const Dashboard = () => {
<span>
Welcome to{" "}
<span style={{ color: "#067ADC" }}>
<NavLink
to={`/profile/${user.result.userId}`}
className="link-primary text-decoration-none"
@ -133,9 +131,35 @@ const Dashboard = () => {
{user.result.title}. {user.result.firstName}{" "}
{user.result.middleName} {user.result.lastName}
</NavLink>
</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>
</span>
</span>
<br />
{/* Dynamic content based on the selected tab */}

View File

@ -8,7 +8,7 @@ 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";
import { deleteFundDetail } from "../redux/features/propertySlice";
const PropertyView = () => {
const { id } = useParams(); // Extract the property ID from the route
@ -17,20 +17,7 @@ const PropertyView = () => {
(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]);
// console.log("usr", user.result.userId);
const [activeTab, setActiveTab] = useState("propertydetails");
@ -48,10 +35,6 @@ const PropertyView = () => {
}
}, [id, dispatch]);
useEffect(() => {
dispatch(fetchFundDetails(id));
}, [dispatch, id]);
useEffect(() => {
if (status === "succeeded") {
setLoading(false);
@ -89,15 +72,30 @@ const PropertyView = () => {
const handleSubmit = (e) => {
e.preventDefault();
if (formData.fundValue <= 0 || formData.fundPercentage <= 0) {
return toast.error("Please enter valid funding details");
}
const fundDetails = {
fundValue: Number(formData.fundValue), // Ensure number conversion if needed
fundPercentage: Number(formData.fundPercentage),
userId: user.result._id,
};
dispatch(addFundDetails({ id, fundDetails, toast }));
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 (
<>
<Navbar />
@ -200,7 +198,7 @@ const PropertyView = () => {
className="fa fa-dollar"
style={{ color: "#F74B02" }}
/>{" "}
{selectedProperty.totalCoststoBuyAtoB}
{selectedProperty?.totalCoststoBuyAtoB || "N/A"}
</span>
</p>
<p
@ -219,7 +217,8 @@ const PropertyView = () => {
className="fa fa-dollar"
style={{ color: "#F74B02" }}
/>{" "}
{selectedProperty.NetProfit}
{/* {selectedProperty.NetProfit} */}
{selectedProperty?.NetProfit || "N/A"}
</span>
</p>
</div>
@ -389,7 +388,6 @@ const PropertyView = () => {
<div>
{activeTab === "Funding Details" && (
<div className="tab-pane active">
<div>
{selectedProperty.fundDetails.length === 0 ? (
<p>No fund details available.</p>
@ -397,36 +395,28 @@ const PropertyView = () => {
<ul>
{selectedProperty.fundDetails.map((detail, index) => (
<li key={index}>
Fund Value: {detail.fundValue}, Fund Percentage: {detail.fundPercentage}%
</li>
))}
</ul>
Fund Value: {detail.fundValue}, Fund Percentage:{" "}
{detail.fundPercentage}%
{user?.result?.userId ? (
<div>
{user?.result?._id === detail.userId && (
<button
onClick={() => handleDelete(detail._id)}
>
Delete
</button>
)}
{fundDetails.length === 0 ? (
<p>No fund details available.</p>
</div>
) : (
<ul>
{fundDetails.map((detail, index) => (
<li key={index}>
Fund Value: {detail.fundValue}, Fund Percentage: {detail.fundPercentage}%
<div></div>
)}
</li>
))}
</ul>
)}
</div>
</div>
)}
</div>
)}
@ -435,7 +425,6 @@ const PropertyView = () => {
</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>

View File

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

View File

@ -58,7 +58,6 @@ export const updateProperty = createAsyncThunk(
try {
const response = await api.updateProperty(id, propertyData);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
@ -66,12 +65,15 @@ export const updateProperty = createAsyncThunk(
);
export const addFundDetails = createAsyncThunk(
'property/addFundDetails',
"property/addFundDetails",
async ({ id, fundDetails, toast }, { rejectWithValue }) => {
try {
const response = await axios.put(
`${import.meta.env.VITE_REACT_APP_SECRET}/properties/${id}/fund-details`,
fundDetails);
`${
import.meta.env.VITE_REACT_APP_SECRET
}/properties/${id}/fund-details`,
fundDetails
);
toast.success("Submitted Successfully");
return response.data;
} catch (error) {
@ -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 () => {
// 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(
'properties/getProperties',
"properties/getProperties",
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;
}
);
const propertySlice = createSlice({
name: "property",
initialState: {
@ -115,6 +144,7 @@ const propertySlice = createSlice({
currentPage: 1,
loading: false,
properties: [],
fundDetails: [],
},
reducers: {},
extraReducers: (builder) => {
@ -191,6 +221,21 @@ const propertySlice = createSlice({
.addCase(addFundDetails.rejected, (state, action) => {
state.loading = false;
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;
});
},
});