done
This commit is contained in:
parent
e66b5fc26d
commit
6b0be193a5
|
@ -0,0 +1,32 @@
|
|||
|
||||
|
||||
import dotenv from "dotenv";
|
||||
import mongoose from 'mongoose';
|
||||
import bcrypt from "bcryptjs";
|
||||
import jwt from "jsonwebtoken";
|
||||
import adminModal from "../models/admin.js"
|
||||
|
||||
dotenv.config();
|
||||
const secret = process.env.SECRET_KEY;
|
||||
|
||||
|
||||
|
||||
export const login = async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
try {
|
||||
const user = await adminModal.findOne({ username });
|
||||
if (!user) return res.status(400).json({ message: "User not found" });
|
||||
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
if (!isMatch) return res.status(400).json({ message: "Invalid password" });
|
||||
|
||||
const token = jwt.sign({ userId: user._id }, secret, { expiresIn: "1h" });
|
||||
res.json({ token });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Server error" });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
@ -241,3 +241,28 @@ export const updateUser = async (req, res) => {
|
|||
res.status(500).json({ message: "Error updating user", error });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const investFunds = async (req, res) => {
|
||||
const { userId,fundAmount } = req.body;
|
||||
|
||||
// console.log("userId", userId, fundAmount);
|
||||
|
||||
if (fundAmount <= 0) {
|
||||
return res.status(400).json({ message: "Investment amount must be greater than 0." });
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedUser = await UserModal.findOneAndUpdate(
|
||||
{ userId }, // Query by custom userId, not ObjectId
|
||||
{ fundAmount},
|
||||
{ new: true } // Return the updated document
|
||||
);
|
||||
if (!updatedUser) {
|
||||
return res.status(404).json({ message: "User not found." });
|
||||
}
|
||||
res.status(200).json(updatedUser);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Something went wrong." });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,7 +7,10 @@ import session from "express-session";
|
|||
import userRouter from "./routes/user.js";
|
||||
import propertyRouter from "./routes/property.js";
|
||||
import mysqlRouter from "./routes/mysqlproperty.js";
|
||||
import adminRouter from "./routes/admin.js"
|
||||
import dotenv from "dotenv";
|
||||
import bcrypt from "bcryptjs";
|
||||
import adminModal from "./models/admin.js";
|
||||
|
||||
const app = express();
|
||||
dotenv.config();
|
||||
|
@ -34,6 +37,7 @@ app.use(
|
|||
app.use("/users", userRouter);
|
||||
app.use("/properties", propertyRouter);
|
||||
app.use("/mysql", mysqlRouter); // Use MySQL routes
|
||||
app.use("/admin", adminRouter);
|
||||
|
||||
|
||||
|
||||
|
@ -65,3 +69,14 @@ dbConnectionPromise
|
|||
console.log(`${error} did not connect`);
|
||||
});
|
||||
|
||||
|
||||
const createAdminUser = async () => {
|
||||
const hashedPassword = await bcrypt.hash("Uer$99#KKma-hi19", 10);
|
||||
const admin = new adminModal({ username: "admin", password: hashedPassword });
|
||||
await admin.save();
|
||||
console.log("Admin user created.", admin);
|
||||
mongoose.disconnect();
|
||||
};
|
||||
|
||||
createAdminUser();
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import mongoose from "mongoose";
|
||||
|
||||
const adminSchema = new mongoose.Schema({
|
||||
username: { type: String, required: true, unique: true },
|
||||
password: { type: String, required: true }
|
||||
});
|
||||
|
||||
export default mongoose.model("Admin", adminSchema);
|
||||
|
||||
|
||||
|
|
@ -30,6 +30,7 @@ const userSchema = new mongoose.Schema({
|
|||
}
|
||||
}
|
||||
],
|
||||
fundAmount: { type: String },
|
||||
});
|
||||
|
||||
export default mongoose.model("User", userSchema);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import { login } from "../controllers/admin.js"
|
||||
|
||||
|
||||
router.post("/login", login);
|
||||
|
||||
|
||||
export default router;
|
|
@ -1,7 +1,7 @@
|
|||
import express from "express";
|
||||
const router = express.Router();
|
||||
|
||||
import { signup, signin, verifyUser, showUser, forgotPassword, resetPassword, updateUser } from "../controllers/user.js";
|
||||
import { signup, signin, verifyUser, showUser, forgotPassword, resetPassword, updateUser,investFunds } from "../controllers/user.js";
|
||||
|
||||
router.post("/signup", signup);
|
||||
router.post("/signin", signin);
|
||||
|
@ -10,6 +10,7 @@ router.get('/:userId', showUser);
|
|||
router.post("/forgotpassword", forgotPassword);
|
||||
router.post("/resetpassword/:id/:token", resetPassword);
|
||||
router.put('/update', updateUser);
|
||||
router.patch("/invest", investFunds);
|
||||
|
||||
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -45,7 +45,7 @@
|
|||
|
||||
|
||||
|
||||
<script type="module" crossorigin src="/assets/index-CFxhPaJf.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Cw_Jz1o9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DUngj0jf.css">
|
||||
</head>
|
||||
|
||||
|
|
|
@ -19,7 +19,9 @@ import PropertyMysqlView from "./components/PropertyMysqlView";
|
|||
import EditProperty from "./components/EditProperty";
|
||||
import SearchProperties from "./components/SearchProperties";
|
||||
import ProfileView from "./components/ProfileView";
|
||||
|
||||
import AdminLogin from "./components/admin/AdminLogin";
|
||||
import ProtectedRoute from "./components/admin/ProtectedRoute";
|
||||
import AdminDashboard from "./components/admin/Dashboard";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
|
@ -32,7 +34,6 @@ const App = () => {
|
|||
<Route path="/contact" element={<Contact />}></Route>
|
||||
<Route path="/register" element={<Register />}></Route>
|
||||
|
||||
|
||||
<Route
|
||||
path="/registrationsuccess"
|
||||
element={
|
||||
|
@ -66,10 +67,29 @@ const App = () => {
|
|||
<Route path="/properties/:house_id" element={<PropertyMysqlView />} />
|
||||
<Route path="/searchmyproperties" element={<SearchMysqlProperties />} />
|
||||
<Route path="/searchproperties" element={<SearchProperties />} />
|
||||
<Route path="/editproperty/:id" element={<PrivateRoute><EditProperty /></PrivateRoute>} />
|
||||
<Route
|
||||
path="/editproperty/:id"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<EditProperty />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/profile/:userId" element={<ProfileView />} />
|
||||
|
||||
<Route path="/AdminPLogin" element={<AdminLogin />} />
|
||||
|
||||
<Route
|
||||
path="/AdminPLogin/dashboard"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AdminDashboard />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@ import UserProfile from "./UserProfile";
|
|||
import { NavLink } from "react-router-dom";
|
||||
import FundingsReceived from "./FundingsReceived";
|
||||
import OffersSubmitted from "./OffersSubmitted";
|
||||
import FundToInvest from "./FundToInvest";
|
||||
|
||||
const Dashboard = () => {
|
||||
const [activeTab, setActiveTab] = useState("dashboard");
|
||||
|
@ -19,6 +20,8 @@ const Dashboard = () => {
|
|||
switch (activeTab) {
|
||||
case "Userdetails":
|
||||
return <UserProfile />;
|
||||
case "FundToInvest":
|
||||
return <FundToInvest />;
|
||||
case "addProperty":
|
||||
return <Addproperty />;
|
||||
case "activeProperties":
|
||||
|
@ -89,6 +92,17 @@ const Dashboard = () => {
|
|||
<span>User Profile</span>
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
className={`btn mt-3 ${
|
||||
activeTab === "FundToInvest" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("FundToInvest")}
|
||||
>
|
||||
<span className="fa fa-home" style={{ color: "#F74B02" }} />
|
||||
<span>Fund To Invest</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`btn mt-3 ${
|
||||
activeTab === "addProperty" ? "active" : ""
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { WillingToInvest } from "../redux/features/authSlice";
|
||||
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const FundToInvest = () => {
|
||||
const { user } = useSelector((state) => ({ ...state.auth }));
|
||||
console.log("user", user);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
userId: user?.result?.userId || "",
|
||||
fundAmount: user?.result?.fundAmount || "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setFormData({
|
||||
userId: user?.result?.userId,
|
||||
fundAmount: user?.result?.fundAmount,
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (formData.fundAmount <= 0) {
|
||||
return toast.error("Please enter a valid funding amount greater than 0");
|
||||
}
|
||||
|
||||
dispatch(WillingToInvest({formData, toast }));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="col-4">
|
||||
<div className="form-floating mb-3">
|
||||
Willing to Invest
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Enter Fund Amount"
|
||||
name="fundAmount"
|
||||
value={formData.fundAmount}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12">
|
||||
<div className="d-grid">
|
||||
<button
|
||||
className="btn btn-primary btn-lg"
|
||||
type="submit"
|
||||
style={{
|
||||
backgroundColor: "#fda417",
|
||||
border: "#fda417",
|
||||
}}
|
||||
>
|
||||
submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FundToInvest;
|
|
@ -40,14 +40,16 @@ const ProfileView = () => {
|
|||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="d-flex flex-column align-items-center text-center">
|
||||
|
||||
<img
|
||||
// src="https://bootdey.com/img/Content/avatar/avatar7.png"
|
||||
src={user.profileImage}
|
||||
alt="Admin"
|
||||
className="rounded-circle"
|
||||
width={150}
|
||||
/>
|
||||
<img
|
||||
className="img-fluid"
|
||||
src={user.profileImage || profilepic}
|
||||
alt="ProfileImage"
|
||||
style={{
|
||||
marginTop: "0px",
|
||||
maxWidth: "300px",
|
||||
maxHeight: "300px",
|
||||
}}
|
||||
/>
|
||||
|
||||
<h4>
|
||||
{user.title}. {user.firstName} {user.middleName}{" "}
|
||||
|
@ -186,7 +188,7 @@ const ProfileView = () => {
|
|||
<div className="col-md-4">
|
||||
<div className="card mb-3">
|
||||
<div className="card-body">
|
||||
<h1
|
||||
<h1
|
||||
className="d-flex align-items-center mb-3"
|
||||
style={{ color: "#fda417", fontSize: "26px" }}
|
||||
>
|
||||
|
@ -197,22 +199,20 @@ const ProfileView = () => {
|
|||
About
|
||||
</i>
|
||||
me :
|
||||
</h1> <hr />
|
||||
{user.aboutme}
|
||||
</h1>{" "}
|
||||
<hr />
|
||||
|
||||
{user.aboutme}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="col-md-4 mb-3">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
|
||||
|
||||
|
||||
<h1
|
||||
<h1
|
||||
className="d-flex align-items-center mb-3"
|
||||
style={{ color: "#fda417", fontSize: "26px" }}
|
||||
>
|
||||
|
@ -226,18 +226,19 @@ const ProfileView = () => {
|
|||
</h1>
|
||||
<hr />
|
||||
|
||||
<h2 className="d-flex flex-column align-items-center text-center"
|
||||
style={{
|
||||
color: "#fda417",
|
||||
border: "#fda417",
|
||||
fontSize: "60px",
|
||||
fontWeight: "normal",
|
||||
}}
|
||||
>
|
||||
$ 500,000
|
||||
</h2>
|
||||
<h2
|
||||
className="d-flex flex-column align-items-center text-center"
|
||||
style={{
|
||||
color: "#fda417",
|
||||
border: "#fda417",
|
||||
fontSize: "60px",
|
||||
fontWeight: "normal",
|
||||
}}
|
||||
>
|
||||
$ {user.fundAmount}
|
||||
</h2>
|
||||
|
||||
<span className="d-flex flex-column align-items-center text-center">
|
||||
{/* <span className="d-flex flex-column align-items-center text-center">
|
||||
<button
|
||||
className="btn btn-primary btn-lg "
|
||||
type="submit"
|
||||
|
@ -248,39 +249,30 @@ const ProfileView = () => {
|
|||
>
|
||||
Request
|
||||
</button>
|
||||
</span>
|
||||
{/* <button className="btn btn-outline-primary">Message</button> */}
|
||||
|
||||
<hr />
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<span className="mb-0">Email</span>
|
||||
</div>
|
||||
{user.email}
|
||||
<div className="col-sm-9 text-secondary"></div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<span className="mb-0">Phone</span>
|
||||
</div>
|
||||
67656584687
|
||||
<div className="col-sm-9 text-secondary"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</span> */}
|
||||
|
||||
<hr />
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<span className="mb-0">Email</span>
|
||||
</div>
|
||||
{user.email}
|
||||
<div className="col-sm-9 text-secondary"></div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<span className="mb-0">Phone</span>
|
||||
</div>
|
||||
67656584687
|
||||
<div className="col-sm-9 text-secondary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="col-md-4 mb-3">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
function AdminLogin() {
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_REACT_APP_SECRET;
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const { data } = await axios.post(`${BASE_URL}/admin/login`, {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
|
||||
localStorage.setItem("token", data.token);
|
||||
navigate("/AdminPLogin/dashboard");
|
||||
} catch (error) {
|
||||
alert("Invalid login credentials", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section
|
||||
className="d-flex justify-content-center align-items-center col-12"
|
||||
style={{
|
||||
minHeight: "100vh",
|
||||
backgroundColor: "#FFFFFF",
|
||||
width: "100%",
|
||||
paddingLeft: "500px",
|
||||
}}
|
||||
>
|
||||
<div className="col-md-6 col-lg-4">
|
||||
<div className="container-fluid px-0">
|
||||
<div className="row gy-4 align-items-center justify-content-center">
|
||||
<div className="col-12 text-center">
|
||||
<input
|
||||
autoComplete="off"
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<br /> <br />
|
||||
|
||||
<input
|
||||
autoComplete="off"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="mb-3">
|
||||
<button onClick={handleSubmit} className="btn btn-dark w-70">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminLogin;
|
|
@ -0,0 +1,20 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
|
||||
const AdminDashboard = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("token");
|
||||
navigate("/AdminPLogin");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Welcome to the Sample Page!</h1>
|
||||
<button onClick={handleLogout}>Logout</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminDashboard;
|
|
@ -0,0 +1,23 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const Loadingredirect = () => {
|
||||
const [count, setCount] = useState(3);
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCount((currentCount) => --currentCount);
|
||||
}, 1000);
|
||||
|
||||
count === 0 && navigate("/login");
|
||||
return () => clearInterval(interval);
|
||||
}, [count, navigate]);
|
||||
return (
|
||||
<div style={{ marginTop: "100px" }}>
|
||||
<h5>Redirecting you in {count} seconds</h5>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default Loadingredirect
|
|
@ -0,0 +1,23 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
function ProtectedRoute({ children }) {
|
||||
const navigate = useNavigate();
|
||||
const token = localStorage.getItem("token");
|
||||
console.log("token", token)
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
navigate("/AdminPLogin"); // Redirect to homepage if token is missing
|
||||
}
|
||||
}, [token, navigate]);
|
||||
|
||||
return token ? children : null;
|
||||
}
|
||||
|
||||
ProtectedRoute.propTypes = {
|
||||
children: PropTypes.node.isRequired, // Validate the presence of children
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
|
@ -0,0 +1 @@
|
|||
body{background: #000}.card{border: none;height: 320px}.forms-inputs{position: relative}.forms-inputs span{position: absolute;top:-18px;left: 10px;background-color: #fff;padding: 5px 10px;font-size: 15px}.forms-inputs input{height: 50px;border: 2px solid #eee}.forms-inputs input:focus{box-shadow: none;outline: none;border: 2px solid #000}.btn{height: 50px}.success-data{display: flex;flex-direction: column}.bxs-badge-check{font-size: 90px}
|
|
@ -30,6 +30,8 @@ export const updateProperty = (id, propertyData) => API.put(`/properties/${id}`,
|
|||
|
||||
export const showUser = (userId) => API.get(`/users/${userId}`);
|
||||
|
||||
export const investFunds = (formData) => API.patch(`/users/invest`, formData);
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -52,6 +52,20 @@ export const updateUser = createAsyncThunk(
|
|||
}
|
||||
);
|
||||
|
||||
export const WillingToInvest = createAsyncThunk(
|
||||
"auth/WillingToInvest",
|
||||
async ({formData, toast }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.investFunds(formData); // Call the backend API
|
||||
toast.success("Investment amount updated successfully!");
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
toast.error(error.response.data.message);
|
||||
return rejectWithValue(error.response.data);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: "auth",
|
||||
|
@ -113,7 +127,22 @@ const authSlice = createSlice({
|
|||
.addCase(updateUser.rejected, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.payload;
|
||||
});
|
||||
})
|
||||
|
||||
.addCase(WillingToInvest.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(WillingToInvest.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.user = { ...state.user, result: action.payload }; // Update the user result with the new data
|
||||
})
|
||||
.addCase(WillingToInvest.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
|
||||
|
||||
;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue