done
This commit is contained in:
parent
a7727d10b7
commit
5f9f99b7a0
|
@ -0,0 +1,28 @@
|
||||||
|
import PropertyModal from "../models/property.js";
|
||||||
|
import UserModal from "../models/user.js";
|
||||||
|
import mongoose from "mongoose";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
export const createProperty = async (req, res) => {
|
||||||
|
const property = req.body;
|
||||||
|
function generateRandomNumber() {
|
||||||
|
return Math.floor(Math.random() * 90000) + 10000;
|
||||||
|
}
|
||||||
|
const randomNumber = generateRandomNumber().toString();
|
||||||
|
const propertyId = `EFP${randomNumber}`;
|
||||||
|
const newProperty = new PropertyModal({
|
||||||
|
...property,
|
||||||
|
creator: req.userId,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
publishedAt: new Date().toISOString(),
|
||||||
|
currentYear: new Date().getFullYear(),
|
||||||
|
propertyId: propertyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await newProperty.save();
|
||||||
|
res.status(201).json(newProperty);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(404).json({ message: "Something went wrong" });
|
||||||
|
}
|
||||||
|
};
|
|
@ -4,6 +4,7 @@ import cors from "cors";
|
||||||
import morgan from "morgan";
|
import morgan from "morgan";
|
||||||
import session from "express-session";
|
import session from "express-session";
|
||||||
import userRouter from "./routes/user.js";
|
import userRouter from "./routes/user.js";
|
||||||
|
import propertyRouter from "./routes/property.js";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ app.use(
|
||||||
);
|
);
|
||||||
|
|
||||||
app.use("/users", userRouter);
|
app.use("/users", userRouter);
|
||||||
|
app.use("/property", propertyRouter);
|
||||||
|
|
||||||
app.get("/", (req, res) => {
|
app.get("/", (req, res) => {
|
||||||
res.send("Welcome to EF-API");
|
res.send("Welcome to EF-API");
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import UserModel from "../models/user.js"
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const secret = process.env.SECRET_KEY
|
||||||
|
|
||||||
|
const auth = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const token = req.headers.authorization.split(" ")[1];
|
||||||
|
const isCustomAuth = token.length < 500;
|
||||||
|
let decodedData;
|
||||||
|
if (token && isCustomAuth) {
|
||||||
|
decodedData = jwt.verify(token, secret);
|
||||||
|
req.userId = decodedData?.id;
|
||||||
|
} else {
|
||||||
|
decodedData = jwt.decode(token);
|
||||||
|
// const googleId = decodedData?.sub.toString();
|
||||||
|
// const user = await UserModel.findOne({ googleId });
|
||||||
|
// req.userId = user?._id;
|
||||||
|
req.userId = decodedData?.id;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default auth;
|
|
@ -0,0 +1,27 @@
|
||||||
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
|
const propertySchema = mongoose.Schema({
|
||||||
|
propertyType: {type: String, required: true },
|
||||||
|
title: {type: String, required: true },
|
||||||
|
yearBuild: {type: String, required: true },
|
||||||
|
totalSqft: {type: String, required: true },
|
||||||
|
propertyId: String,
|
||||||
|
creator: String,
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
default: new Date(),
|
||||||
|
},
|
||||||
|
publishedAt: {
|
||||||
|
type: Date,
|
||||||
|
default: new Date(),
|
||||||
|
},
|
||||||
|
currentYear: {
|
||||||
|
type: Number,
|
||||||
|
default: new Date().getFullYear(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const PropertyModal = mongoose.model("property", propertySchema);
|
||||||
|
|
||||||
|
export default PropertyModal;
|
|
@ -18,7 +18,8 @@
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mongoose": "^8.6.0",
|
"mongoose": "^8.6.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"nodemailer": "^6.9.14"
|
"nodemailer": "^6.9.14",
|
||||||
|
"uuid": "^10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mongodb-js/saslprep": {
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
|
@ -1241,6 +1242,19 @@
|
||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mongoose": "^8.6.0",
|
"mongoose": "^8.6.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"nodemailer": "^6.9.14"
|
"nodemailer": "^6.9.14",
|
||||||
|
"uuid": "^10.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import auth from '../middleware/auth.js';
|
||||||
|
import { createProperty } from '../controllers/property.js';
|
||||||
|
|
||||||
|
router.post('/', auth, createProperty);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default router;
|
File diff suppressed because one or more lines are too long
|
@ -42,7 +42,7 @@
|
||||||
<!-- <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script> -->
|
<!-- <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script> -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<script type="module" crossorigin src="/assets/index-CCiNHmF-.js"></script>
|
<script type="module" crossorigin src="/assets/index-BdFlv2f8.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-B4N2BY33.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-B4N2BY33.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ const App = () => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,74 +1,121 @@
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import "../addproperty.css"
|
import { useDispatch } from "react-redux";
|
||||||
|
import { createProperty } from "../redux/features/propertySlice";
|
||||||
|
import "../addproperty.css";
|
||||||
|
|
||||||
const Addproperty = () => {
|
const Addproperty = () => {
|
||||||
const [activeTab, setActiveTab] = useState('billing');
|
|
||||||
|
|
||||||
const handleContinue = () => {
|
const [formData, setFormData] = useState({
|
||||||
if (activeTab === 'billing') setActiveTab('shipping');
|
propertyType: "",
|
||||||
else if (activeTab === 'shipping') setActiveTab('review');
|
title: "",
|
||||||
|
yearBuild: "",
|
||||||
|
totalSqft: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
if (activeTab === 'review') setActiveTab('shipping');
|
|
||||||
else if (activeTab === 'shipping') setActiveTab('billing');
|
const handlePlaceOrder = (e) => {
|
||||||
|
e.preventDefault(); // Prevent form from refreshing the page
|
||||||
|
dispatch(createProperty(formData));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container tabs-wrap">
|
<div className="container tabs-wrap">
|
||||||
<ul className="nav nav-tabs" role="tablist">
|
|
||||||
<li role="presentation" className={activeTab === 'billing' ? 'active tab' : 'tab'}>
|
|
||||||
<a onClick={() => setActiveTab('billing')} role="tab">Property Address</a>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" className={activeTab === 'shipping' ? 'active tab' : 'tab'}>
|
|
||||||
<a onClick={() => setActiveTab('shipping')} role="tab">Request Investment Amount</a>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" className={activeTab === 'review' ? 'active tab' : 'tab'}>
|
|
||||||
<a onClick={() => setActiveTab('review')} role="tab">Review & Document</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
|
<form onSubmit={handlePlaceOrder}>
|
||||||
<div className="tab-content">
|
<div className="tab-content">
|
||||||
{activeTab === 'billing' && (
|
|
||||||
<div role="tabpanel" className="tab-pane active">
|
<div role="tabpanel" className="tab-pane active">
|
||||||
<h3>Property Details</h3>
|
<h3>Submit the Property Details</h3>
|
||||||
<p>Property Address Form</p>
|
<br />
|
||||||
<button className="btn btn-primary continue" style={{
|
<div className="row gy-3 overflow-hidden">
|
||||||
backgroundColor: "#fda417",
|
<div className="col-12">
|
||||||
border: "#fda417",
|
<select
|
||||||
}} onClick={handleContinue}>Continue</button>
|
className="form-floating mb-3 form-control"
|
||||||
|
name="propertyType"
|
||||||
|
value={formData.propertyType}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">Please Select Property Type</option>
|
||||||
|
<option value="Residential">Residential</option>
|
||||||
|
<option value="Land">Land</option>
|
||||||
|
<option value="Commercial">Commercial</option>
|
||||||
|
<option value="Industrial">Industrial</option>
|
||||||
|
<option value="Water">Water</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="col-12">
|
||||||
{activeTab === 'shipping' && (
|
<div className="form-floating mb-3">
|
||||||
<div role="tabpanel" className="tab-pane active">
|
<input
|
||||||
<h3>Investment Details</h3>
|
type="text"
|
||||||
<p>Investment Details Form</p>
|
className="form-control"
|
||||||
<button className="btn btn-primary back" style={{
|
name="title"
|
||||||
backgroundColor: "#fda417",
|
placeholder="Property title"
|
||||||
border: "#fda417",
|
value={formData.title}
|
||||||
}} onClick={handleBack}>Go Back</button>{" "}
|
onChange={handleInputChange}
|
||||||
<button className="btn btn-primary continue" style={{
|
required
|
||||||
backgroundColor: "#fda417",
|
/>
|
||||||
border: "#fda417",
|
<label htmlFor="title" className="form-label">
|
||||||
}} onClick={handleContinue}>Continue</button>
|
Property Title
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{activeTab === 'review' && (
|
|
||||||
<div role="tabpanel" className="tab-pane active">
|
|
||||||
<h3>Review & Document</h3>
|
|
||||||
<p>Review & Document Tab</p>
|
|
||||||
<button className="btn btn-primary back" style={{
|
|
||||||
backgroundColor: "#fda417",
|
|
||||||
border: "#fda417",
|
|
||||||
}} onClick={handleBack}>Go Back</button>{" "}
|
|
||||||
<button className="btn btn-primary continue" style={{
|
|
||||||
backgroundColor: "#fda417",
|
|
||||||
border: "#fda417",
|
|
||||||
}} >Place Order</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="col-12">
|
||||||
|
<div className="form-floating mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
name="yearBuild"
|
||||||
|
placeholder="Year build"
|
||||||
|
value={formData.yearBuild}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label htmlFor="yearBuild" className="form-label">
|
||||||
|
Year Build
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="form-floating mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
name="totalSqft"
|
||||||
|
placeholder="Total SQFT"
|
||||||
|
value={formData.totalSqft}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label htmlFor="totalSqft" className="form-label">
|
||||||
|
Total SQFT
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary continue"
|
||||||
|
style={{ backgroundColor: "#fda417", border: "#fda417" }}
|
||||||
|
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,4 +19,5 @@ API.interceptors.request.use((req) => {
|
||||||
export const signUp = (formData) => API.post("/users/signup", formData);
|
export const signUp = (formData) => API.post("/users/signup", formData);
|
||||||
export const signIn = (formData) => API.post("/users/signin", formData);
|
export const signIn = (formData) => API.post("/users/signin", formData);
|
||||||
export const verifyEmail = (id, token, data) => API.get(`/users/${id}/verify/${token}`, data);
|
export const verifyEmail = (id, token, data) => API.get(`/users/${id}/verify/${token}`, data);
|
||||||
|
export const createProperty = (propertyData) => API.post("/property", propertyData);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
|
import * as api from "../api";
|
||||||
|
|
||||||
|
// Async thunk for submitting the form
|
||||||
|
export const createProperty = createAsyncThunk(
|
||||||
|
"property/createProperty",
|
||||||
|
async (formData, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await api.createProperty(formData);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return rejectWithValue(error.response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const propertySlice = createSlice({
|
||||||
|
name: "property",
|
||||||
|
initialState: {
|
||||||
|
data: {},
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder
|
||||||
|
.addCase(createProperty.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
})
|
||||||
|
.addCase(createProperty.fulfilled, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.data = action.payload;
|
||||||
|
})
|
||||||
|
.addCase(createProperty.rejected, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default propertySlice.reducer;
|
|
@ -1,11 +1,13 @@
|
||||||
import { configureStore } from "@reduxjs/toolkit";
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
import AuthReducer from "./features/authSlice";
|
import AuthReducer from "./features/authSlice";
|
||||||
import userReducer from "./features/userSlice";
|
import userReducer from "./features/userSlice";
|
||||||
|
import propertyReducer from "./features/propertySlice";
|
||||||
|
|
||||||
export default configureStore({
|
export default configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
auth: AuthReducer,
|
auth: AuthReducer,
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
|
property: propertyReducer,
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@ export default defineConfig({
|
||||||
|
|
||||||
'/users/signup': 'http://67.225.129.127:3002/',
|
'/users/signup': 'http://67.225.129.127:3002/',
|
||||||
'/users/signin': 'http://67.225.129.127:3002/',
|
'/users/signin': 'http://67.225.129.127:3002/',
|
||||||
|
'/property': 'http://67.225.129.127:3002/',
|
||||||
// '/users/forgotpassword': 'http://67.225.129.127:3002/',
|
// '/users/forgotpassword': 'http://67.225.129.127:3002/',
|
||||||
// '/users/:id/verify/:token': 'http://67.225.129.127:3002/',
|
// '/users/:id/verify/:token': 'http://67.225.129.127:3002/',
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue