done
This commit is contained in:
parent
8b49882f18
commit
c43f306d57
|
@ -1,5 +1,6 @@
|
|||
import dotenv from "dotenv";
|
||||
import UserModal from "../models/user.js";
|
||||
import mongoose from 'mongoose';
|
||||
import bcrypt from "bcryptjs";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { sendEmail } from "../utils/sendEmail.js";
|
||||
|
@ -119,22 +120,35 @@ export const signin = async (req, res) => {
|
|||
//To show user
|
||||
|
||||
export const showUser = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { userId } = req.params;
|
||||
|
||||
// Optional: Validate if userId is a MongoDB ObjectId
|
||||
if (mongoose.Types.ObjectId.isValid(userId)) {
|
||||
try {
|
||||
|
||||
const user = await UserModal.findById({id});
|
||||
|
||||
const user = await UserModal.findById(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
|
||||
res.status(200).json(user);
|
||||
return res.json(user);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ message: "Internal servers error" });
|
||||
return res.status(500).json({ message: 'Server Error' });
|
||||
}
|
||||
} else {
|
||||
// If the userId is not an ObjectId, search by other fields (e.g., custom userId)
|
||||
try {
|
||||
const user = await UserModal.findOne({ userId }); // Adjust based on how your schema stores userId
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
return res.json(user);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: 'Server Error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//forgot password
|
||||
|
||||
export const forgotPassword = async (req, res) => {
|
||||
|
@ -196,3 +210,27 @@ export const resetPassword = async (req, res) => {
|
|||
res.status(500).json({ message: "Something went wrong" });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Update user controller
|
||||
|
||||
export const updateUser = async (req, res) => {
|
||||
try {
|
||||
const { userId, title, firstName, middleName, lastName, email, aboutme, profileImage } = req.body;
|
||||
// Use findOneAndUpdate instead, querying by userId (custom field)
|
||||
const updatedUser = await UserModal.findOneAndUpdate(
|
||||
{ userId }, // Query by custom userId, not ObjectId
|
||||
{ title, firstName, middleName, lastName, email, aboutme, profileImage },
|
||||
{ new: true } // Return the updated document
|
||||
);
|
||||
|
||||
if (!updatedUser) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
|
||||
res.status(200).json(updatedUser);
|
||||
} catch (error) {
|
||||
console.error("Error updating user:", error);
|
||||
res.status(500).json({ message: "Error updating user", error });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,6 +6,9 @@ const userSchema = new mongoose.Schema({
|
|||
middleName: { type: String, required: true },
|
||||
lastName: { type: String, required: true },
|
||||
email: { type: String, required: true, unique: true },
|
||||
profileImage:String,
|
||||
// profileImage: { type: String, required: true, unique: true },
|
||||
aboutme: { type: String, required: true, unique: true },
|
||||
password: { type: String, required: true },
|
||||
termsconditions:{type: String,},
|
||||
userType:{ type: String, required: true },
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import express from "express";
|
||||
const router = express.Router();
|
||||
|
||||
import { signup, signin, verifyUser, showUser, forgotPassword, resetPassword, } from "../controllers/user.js";
|
||||
import { signup, signin, verifyUser, showUser, forgotPassword, resetPassword, updateUser } from "../controllers/user.js";
|
||||
|
||||
router.post("/signin", signin);
|
||||
router.post("/signup", signup);
|
||||
router.get('/:id/verify/:token/', verifyUser);
|
||||
router.get('/:id', showUser);
|
||||
router.get('/:userId', showUser);
|
||||
router.post("/forgotpassword", forgotPassword);
|
||||
router.post("/resetpassword/:id/:token", resetPassword);
|
||||
router.put('/update', updateUser);
|
||||
|
||||
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -45,8 +45,8 @@
|
|||
|
||||
|
||||
|
||||
<script type="module" crossorigin src="/assets/index-BsVOnNCb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DepkKhoc.css">
|
||||
<script type="module" crossorigin src="/assets/index-DPPwfV95.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-iEl-il0E.css">
|
||||
</head>
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import Services from "./components/Services";
|
|||
import PropertyMysqlView from "./components/PropertyMysqlView";
|
||||
import EditProperty from "./components/EditProperty";
|
||||
import SearchProperties from "./components/SearchProperties";
|
||||
import ProfileView from "./components/ProfileView";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
|
@ -63,8 +64,10 @@ 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="/profile/:userId" element={<ProfileView />} />
|
||||
|
||||
<Route path="/editproperty/:id" element={<EditProperty />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
|
|
@ -1,92 +1,37 @@
|
|||
import { useState } from "react";
|
||||
import Footer from "./Footer";
|
||||
import Navbar from "./Navbar";
|
||||
import { useSelector } from "react-redux";
|
||||
import profilepic from "../img/samplepic.jpg";
|
||||
import Addproperty from "./Addproperty";
|
||||
import UserProperties from "./UserProperties";
|
||||
// import { fetchUserProperties } from "../redux/features/propertySlice";
|
||||
import "../dashboard.css";
|
||||
import { useSelector } from "react-redux";
|
||||
import UserProfile from "./UserProfile";
|
||||
import { NavLink } from "react-router-dom";
|
||||
|
||||
const Dashboard = () => {
|
||||
// const dispatch = useDispatch();
|
||||
const { user } = useSelector((state) => ({ ...state.auth }));
|
||||
// const { userProperties} = useSelector((state) => state.property);
|
||||
const [activeTab, setActiveTab] = useState("dashboard");
|
||||
|
||||
// Fetch user properties when "Active Properties" tab is selected
|
||||
// useEffect(() => {
|
||||
// if (activeTab === "activeProperties") {
|
||||
// dispatch(fetchUserProperties(user?.result?.userId));
|
||||
// }
|
||||
// }, [activeTab, dispatch, user?.result?.userId]);
|
||||
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const renderTabContent = () => {
|
||||
switch (activeTab) {
|
||||
case "Userdetails":
|
||||
return <UserProfile />;
|
||||
|
||||
case "addProperty":
|
||||
return <Addproperty />;
|
||||
case "activeProperties":
|
||||
return <div>
|
||||
<h3>Active Properties</h3>
|
||||
{/* {userProperties.length > 0 ? (
|
||||
<ul>
|
||||
{userProperties.map((property) => (
|
||||
<li key={property._id}>{property.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>No active properties found.</p>
|
||||
)} */}
|
||||
|
||||
<UserProperties />
|
||||
</div>;
|
||||
case "closedProperties":
|
||||
return <p>These are your closed properties.</p>;
|
||||
default:
|
||||
case "activeProperties":
|
||||
return (
|
||||
<div className="d-flex justify-content-center mt-7 gap-2 p-3">
|
||||
<div className="col-md-6">
|
||||
<div className="card cardchildchild p-2">
|
||||
<div className="profile1">
|
||||
<img
|
||||
src="https://i.imgur.com/NI5b1NX.jpg"
|
||||
height={90}
|
||||
width={90}
|
||||
className="rounded-circle"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex flex-column justify-content-center align-items-center mt-5">
|
||||
<span className="name">Bess Wills</span>
|
||||
<span className="mt-1 braceletid">Bracelet ID: SFG 38393</span>
|
||||
<span className="dummytext mt-3 p-3">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Text elit more smtit. Kimto lee.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="card cardchildchild p-2">
|
||||
<div className="profile1">
|
||||
<img
|
||||
src="https://i.imgur.com/YyoCGsa.jpg"
|
||||
height={90}
|
||||
width={90}
|
||||
className="rounded-circle"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex flex-column justify-content-center align-items-center mt-5">
|
||||
<span className="name">Bess Wills</span>
|
||||
<span className="mt-1 braceletid">Bracelet ID: SFG 38393</span>
|
||||
<span className="dummytext mt-3 p-3">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Text elit more smtit. Kimto lee.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Active Properties</h3>
|
||||
<UserProperties />
|
||||
</div>
|
||||
);
|
||||
case "closedProperties":
|
||||
return <p>These are your closed properties.</p>;
|
||||
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -105,7 +50,7 @@ const Dashboard = () => {
|
|||
<div className="card card1 p-5">
|
||||
<img
|
||||
className="img-fluid"
|
||||
src={profilepic}
|
||||
src={user.result.profileImage || profilepic}
|
||||
alt="ProfileImage"
|
||||
style={{
|
||||
marginTop: "0px",
|
||||
|
@ -125,21 +70,37 @@ const Dashboard = () => {
|
|||
</button>
|
||||
|
||||
<button
|
||||
className={`btn mt-3 ${activeTab === "addProperty" ? "active" : ""}`}
|
||||
className={`btn mt-3 ${
|
||||
activeTab === "Userdetails" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("Userdetails")}
|
||||
>
|
||||
<span className="fa fa-home" style={{ color: "#F74B02" }} />
|
||||
<span>User Profile</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`btn mt-3 ${
|
||||
activeTab === "addProperty" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("addProperty")}
|
||||
>
|
||||
<span className="fa fa-home" style={{ color: "#F74B02" }} />
|
||||
<span>Add Property</span>
|
||||
</button>
|
||||
<button
|
||||
className={`btn mt-3 ${activeTab === "activeProperties" ? "active" : ""}`}
|
||||
className={`btn mt-3 ${
|
||||
activeTab === "activeProperties" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("activeProperties")}
|
||||
>
|
||||
<span className="fa fa-home" style={{ color: "#F74B02" }} />
|
||||
<span>Active Properties</span>
|
||||
</button>
|
||||
<button
|
||||
className={`btn mt-3 ${activeTab === "closedProperties" ? "active" : ""}`}
|
||||
className={`btn mt-3 ${
|
||||
activeTab === "closedProperties" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("closedProperties")}
|
||||
>
|
||||
<span className="fa fa-home" style={{ color: "#F74B02" }} />
|
||||
|
@ -158,23 +119,31 @@ const Dashboard = () => {
|
|||
<div className="col-md-9">
|
||||
<div className="card card2 p-1">
|
||||
{/* Static Dashboard greeting */}
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
Welcome to{" "}
|
||||
<span style={{ color: "#067ADC" }}>
|
||||
{user.result.title}. {user.result.firstName} {user.result.middleName} {user.result.lastName}
|
||||
|
||||
|
||||
<NavLink
|
||||
to={`/profile/${user.result.userId}`}
|
||||
className="link-primary text-decoration-none"
|
||||
target="_blank"
|
||||
>
|
||||
{user.result.title}. {user.result.firstName}{" "}
|
||||
{user.result.middleName} {user.result.lastName}
|
||||
</NavLink>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
<br />
|
||||
|
||||
|
||||
{/* Dynamic content based on the selected tab */}
|
||||
<div className="tab-content p-2">
|
||||
|
||||
{renderTabContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -3,6 +3,8 @@ import { useDispatch, useSelector } from "react-redux";
|
|||
import { fetchPropertyById } from "../redux/features/propertySlice";
|
||||
import { updateProperty } from "../redux/features/propertySlice";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Navbar from "./Navbar";
|
||||
import Footer from "./Footer";
|
||||
|
||||
const EditProperty = () => {
|
||||
const { id } = useParams();
|
||||
|
@ -40,6 +42,11 @@ const EditProperty = () => {
|
|||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<br /> <br /> <br /> <br /> <br /> <br />
|
||||
|
||||
|
||||
<div className="edit-property-form">
|
||||
<h2>Edit Property</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
|
@ -95,6 +102,8 @@ const EditProperty = () => {
|
|||
<button type="submit">Update Property</button>
|
||||
</form>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -67,7 +67,10 @@ const Navbar = () => {
|
|||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/services" className="nav-link">
|
||||
<NavLink to="/services" className="nav-link" style={{
|
||||
fontSize: "20px",
|
||||
fontWeight: "normal",
|
||||
}}>
|
||||
Services
|
||||
</NavLink>
|
||||
</li>
|
||||
|
@ -86,7 +89,10 @@ const Navbar = () => {
|
|||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/about" className="nav-link">
|
||||
<NavLink to="/about" className="nav-link" style={{
|
||||
fontSize: "20px",
|
||||
fontWeight: "normal",
|
||||
}}>
|
||||
About
|
||||
</NavLink>
|
||||
</li>
|
||||
|
@ -107,7 +113,10 @@ const Navbar = () => {
|
|||
</li>
|
||||
|
||||
<li className="nav-item">
|
||||
<NavLink to="/contact" className="nav-link">
|
||||
<NavLink to="/contact" className="nav-link" style={{
|
||||
fontSize: "20px",
|
||||
fontWeight: "normal",
|
||||
}}>
|
||||
Contact
|
||||
</NavLink>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { showUser } from "../redux/features/userSlice";
|
||||
import { useParams } from "react-router-dom";
|
||||
import "../profileview.css";
|
||||
import Navbar from "./Navbar";
|
||||
|
||||
const ProfileView = () => {
|
||||
const { userId } = useParams(); // Extract the userId from the route
|
||||
const dispatch = useDispatch();
|
||||
const { user, loading } = useSelector((state) => state.user); // Access the loading state as well
|
||||
console.log("user", user);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch the user by ID when the component loads
|
||||
if (userId) {
|
||||
dispatch(showUser(userId));
|
||||
}
|
||||
}, [userId, dispatch]);
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>; // Show a loading message while data is being fetched
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div className="main-body">
|
||||
{/* /Breadcrumb */}
|
||||
<div className="row gutters-sm">
|
||||
<div className="col-md-4 mb-3">
|
||||
<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}
|
||||
/>
|
||||
<div className="mt-3">
|
||||
<h4>John Doe</h4>
|
||||
<p className="text-secondary mb-1">Full Stack Developer</p>
|
||||
<p className="text-muted font-size-sm">
|
||||
Bay Area, San Francisco, CA
|
||||
</p>
|
||||
{/* <button className="btn btn-outline-primary">Message</button> */}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-8">
|
||||
<div className="card mb-3">
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<h6 className="mb-0">Full Name</h6>
|
||||
</div>
|
||||
<div className="col-sm-9 text-secondary">Kenneth Valdez</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<h6 className="mb-0">Email</h6>
|
||||
</div>
|
||||
<div className="col-sm-9 text-secondary">fip@jukmuh.al</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<h6 className="mb-0">Phone</h6>
|
||||
</div>
|
||||
<div className="col-sm-9 text-secondary">(239) 816-9029</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<h6 className="mb-0">Mobile</h6>
|
||||
</div>
|
||||
<div className="col-sm-9 text-secondary">(320) 380-4539</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<div className="col-sm-3">
|
||||
<h6 className="mb-0">Address</h6>
|
||||
</div>
|
||||
<div className="col-sm-9 text-secondary">
|
||||
Bay Area, San Francisco, CA
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="row gutters-sm">
|
||||
<div className="col-sm-6 mb-3">
|
||||
<div className="card h-100">
|
||||
<div className="card-body">
|
||||
<h6 className="d-flex align-items-center mb-3">
|
||||
<i className="material-icons text-info mr-2">
|
||||
assignment
|
||||
</i>
|
||||
Project Status
|
||||
</h6>
|
||||
<small>Web Design</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "80%" }}
|
||||
aria-valuenow={80}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
<small>Website Markup</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "72%" }}
|
||||
aria-valuenow={72}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
<small>One Page</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "89%" }}
|
||||
aria-valuenow={89}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
<small>Mobile Template</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "55%" }}
|
||||
aria-valuenow={55}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
<small>Backend API</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "66%" }}
|
||||
aria-valuenow={66}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-6 mb-3">
|
||||
<div className="card h-100">
|
||||
<div className="card-body">
|
||||
<h6 className="d-flex align-items-center mb-3">
|
||||
<i className="material-icons text-info mr-2">
|
||||
assignment
|
||||
</i>
|
||||
Project Status
|
||||
</h6>
|
||||
<small>Web Design</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "80%" }}
|
||||
aria-valuenow={80}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
<small>Website Markup</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "72%" }}
|
||||
aria-valuenow={72}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
<small>One Page</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "89%" }}
|
||||
aria-valuenow={89}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
<small>Mobile Template</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "55%" }}
|
||||
aria-valuenow={55}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
<small>Backend API</small>
|
||||
<div className="progress mb-3" style={{ height: "5px" }}>
|
||||
<div
|
||||
className="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style={{ width: "66%" }}
|
||||
aria-valuenow={66}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileView;
|
|
@ -0,0 +1,201 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateUser } from "../redux/features/authSlice";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import FileBase from "react-file-base64";
|
||||
|
||||
const UserProfile = () => {
|
||||
const { user, isLoading, error } = useSelector((state) => state.auth);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
userId: user?.result?.userId || "",
|
||||
title: user?.result?.title || "",
|
||||
firstName: user?.result?.firstName || "",
|
||||
middleName: user?.result?.middleName || "",
|
||||
lastName: user?.result?.lastName || "",
|
||||
email: user?.result?.email || "",
|
||||
aboutme: user?.result?.aboutme || "",
|
||||
profileImage: user?.result?.profileImage || "", // For storing the image
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setFormData({
|
||||
userId: user.result.userId,
|
||||
title: user.result.title,
|
||||
firstName: user.result.firstName,
|
||||
middleName: user.result.middleName,
|
||||
lastName: user.result.lastName,
|
||||
email: user.result.email,
|
||||
aboutme: user.result.aboutme,
|
||||
profileImage: user.result.profileImage,
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleImageDelete = () => {
|
||||
setFormData({ ...formData, profileImage: "" }); // Reset the profileImage field
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
dispatch(updateUser(formData)); // Dispatching updateUser with form data
|
||||
navigate("/login");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="col-4">
|
||||
Title
|
||||
<select
|
||||
className="form-floating mb-3 form-control"
|
||||
aria-label="Default select example"
|
||||
name="title"
|
||||
value={formData.title}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="None">Please Select Title</option>
|
||||
<option value="Dr">Dr</option>
|
||||
<option value="Prof">Prof</option>
|
||||
<option value="Mr">Mr</option>
|
||||
<option value="Miss">Miss</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<div className="form-floating mb-3">
|
||||
First Name
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="First Name"
|
||||
required="required"
|
||||
name="firstName"
|
||||
value={formData.firstName}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<div className="form-floating mb-3">
|
||||
Middle Name
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Middle Name"
|
||||
required="required"
|
||||
name="middleName"
|
||||
value={formData.middleName}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<div className="form-floating mb-3">
|
||||
Last Name
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Last Name"
|
||||
required="required"
|
||||
name="lastName"
|
||||
value={formData.lastName}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<div className="form-floating mb-3">
|
||||
Email
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Email"
|
||||
required="required"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<div className="form-floating mb-3">
|
||||
About me
|
||||
<textarea
|
||||
type="text"
|
||||
id="aboutme"
|
||||
name="aboutme"
|
||||
className="form-control h-100"
|
||||
value={formData.aboutme}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Profile Image Upload Section */}
|
||||
<div className="col-4">
|
||||
<label>Profile Image</label>
|
||||
<div className="mb-3">
|
||||
<FileBase
|
||||
type="file"
|
||||
multiple={false}
|
||||
onDone={({ base64 }) =>
|
||||
setFormData({ ...formData, profileImage: base64 })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Display Preview of Uploaded Image */}
|
||||
{formData.profileImage && (
|
||||
<div className="col-4 mb-3">
|
||||
<img
|
||||
src={formData.profileImage}
|
||||
alt="Profile Preview"
|
||||
style={{ width: "150px", height: "150px", borderRadius: "50%" }}
|
||||
/>
|
||||
{/* Delete Image Button */}
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger mt-2"
|
||||
onClick={handleImageDelete}
|
||||
>
|
||||
Delete Image
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-12">
|
||||
<div className="d-grid">
|
||||
<button
|
||||
className="btn btn-primary btn-lg"
|
||||
type="submit"
|
||||
style={{
|
||||
backgroundColor: "#fda417",
|
||||
border: "#fda417",
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? "Updating..." : "Update"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{error && <p>{error}</p>}
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserProfile;
|
|
@ -112,7 +112,6 @@ const UserProperties = () => {
|
|||
|
||||
<NavLink
|
||||
to={`/editproperty/${property.propertyId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<button
|
||||
className="btn btn-outline-primary btn-sm mt-2"
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
body{
|
||||
margin-top:20px;
|
||||
color: #1a202c;
|
||||
text-align: left;
|
||||
background-color: #e2e8f0;
|
||||
}
|
||||
.main-body {
|
||||
padding: 15px;
|
||||
}
|
||||
.card {
|
||||
box-shadow: 0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px 0 rgba(0,0,0,.06);
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
background-color: #fff;
|
||||
background-clip: border-box;
|
||||
border: 0 solid rgba(0,0,0,.125);
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 1px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.gutters-sm {
|
||||
margin-right: -8px;
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.gutters-sm>.col, .gutters-sm>[class*=col-] {
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
.mb-3, .my-3 {
|
||||
margin-bottom: 1rem!important;
|
||||
}
|
||||
|
||||
.bg-gray-300 {
|
||||
background-color: #e2e8f0;
|
||||
}
|
||||
.h-100 {
|
||||
height: 100%!important;
|
||||
}
|
||||
.shadow-none {
|
||||
box-shadow: none!important;
|
||||
}
|
|
@ -25,6 +25,7 @@ export const submitProperty = (propertyData) => API.post("/properties", property
|
|||
export const fetchUserProperties = (userId, page, limit) => API.get( `/properties/user/${userId}?page=${page}&limit=${limit}`, userId);
|
||||
export const fetchPropertyById = (id) => API.get(`/properties/${id}`, id);
|
||||
export const updateProperty = (id, propertyData) => API.put(`/properties/${id}`, propertyData);
|
||||
export const showUser = (userId) => API.get(`/users/${userId}`);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import * as api from "../api";
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
|
||||
export const login = createAsyncThunk(
|
||||
|
@ -29,24 +31,30 @@ export const register = createAsyncThunk(
|
|||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Thunk to update user details
|
||||
export const updateUser = createAsyncThunk(
|
||||
"auth/updateUser",
|
||||
async ({ id, data }, { rejectWithValue }) => {
|
||||
'auth/updateUser',
|
||||
async (updatedUserData, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.updateuser(data, id);
|
||||
const response = await axios.put(`http://localhost:3002/users/update`, updatedUserData);
|
||||
// console.log('Update user response:', response.data);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return rejectWithValue(err.response.data);
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response.data);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: "auth",
|
||||
initialState: {
|
||||
user: null,
|
||||
error: "",
|
||||
loading: false,
|
||||
isLoading: false,
|
||||
},
|
||||
reducers: {
|
||||
setUser: (state, action) => {
|
||||
|
@ -59,6 +67,10 @@ const authSlice = createSlice({
|
|||
setUserDetails: (state, action) => {
|
||||
state.user = action.payload;
|
||||
},
|
||||
updateUser: (state, action) => {
|
||||
const updatedUser = action.payload;
|
||||
state.user = { ...state.user, ...updatedUser }; // Update user state without removing it
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
|
@ -87,15 +99,15 @@ const authSlice = createSlice({
|
|||
state.error = action.payload.message;
|
||||
})
|
||||
.addCase(updateUser.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.isLoading = true;
|
||||
})
|
||||
.addCase(updateUser.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.user = action.payload;
|
||||
state.isLoading = false;
|
||||
state.user = action.payload; // Update the user state with the new data
|
||||
})
|
||||
.addCase(updateUser.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload.message;
|
||||
state.isLoading = false;
|
||||
state.error = action.payload;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import * as api from "../api";
|
||||
|
||||
export const showUser = createAsyncThunk(
|
||||
"user/showUser",
|
||||
async ({ id, data }, { rejectWithValue }) => {
|
||||
|
||||
export const showUser = createAsyncThunk('user/showUser', async (userId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.showUser(data, id);
|
||||
console.log("dsdsdsds22", response);
|
||||
const response = await api.showUser(userId);
|
||||
// console.log("response", response);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return rejectWithValue(err.response.data);
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response.data);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export const verifyEmail = createAsyncThunk(
|
||||
"user/verifyEmail",
|
||||
|
@ -33,23 +31,23 @@ const userSlice = createSlice({
|
|||
error: "",
|
||||
loading: false,
|
||||
verified: false,
|
||||
user: null,
|
||||
},
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(showUser.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(showUser.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
localStorage.setItem("profile", JSON.stringify({ ...action.payload }));
|
||||
state.users = Array.isArray(action.payload) ? action.payload : [];
|
||||
state.user = action.payload;
|
||||
})
|
||||
.addCase(showUser.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
|
||||
.addCase(verifyEmail.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
|
|
Loading…
Reference in New Issue