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 session from "express-session";
|
||||
import userRouter from "./routes/user.js";
|
||||
import propertyRouter from "./routes/property.js";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
|
||||
|
@ -32,6 +33,7 @@ app.use(
|
|||
);
|
||||
|
||||
app.use("/users", userRouter);
|
||||
app.use("/property", propertyRouter);
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
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",
|
||||
"mongoose": "^8.6.0",
|
||||
"morgan": "^1.10.0",
|
||||
"nodemailer": "^6.9.14"
|
||||
"nodemailer": "^6.9.14",
|
||||
"uuid": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
|
@ -1241,6 +1242,19 @@
|
|||
"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": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"jsonwebtoken": "^9.0.2",
|
||||
"mongoose": "^8.6.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://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">
|
||||
</head>
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ const App = () => {
|
|||
|
||||
|
||||
|
||||
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
)
|
||||
|
|
|
@ -1,75 +1,122 @@
|
|||
import { useState } from 'react';
|
||||
import "../addproperty.css"
|
||||
import { useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { createProperty } from "../redux/features/propertySlice";
|
||||
import "../addproperty.css";
|
||||
|
||||
const Addproperty = () => {
|
||||
const [activeTab, setActiveTab] = useState('billing');
|
||||
|
||||
const handleContinue = () => {
|
||||
if (activeTab === 'billing') setActiveTab('shipping');
|
||||
else if (activeTab === 'shipping') setActiveTab('review');
|
||||
};
|
||||
const [formData, setFormData] = useState({
|
||||
propertyType: "",
|
||||
title: "",
|
||||
yearBuild: "",
|
||||
totalSqft: "",
|
||||
});
|
||||
|
||||
const handleBack = () => {
|
||||
if (activeTab === 'review') setActiveTab('shipping');
|
||||
else if (activeTab === 'shipping') setActiveTab('billing');
|
||||
};
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<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>
|
||||
const handleInputChange = (e) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
<div className="tab-content">
|
||||
{activeTab === 'billing' && (
|
||||
<div role="tabpanel" className="tab-pane active">
|
||||
<h3>Property Details</h3>
|
||||
<p>Property Address Form</p>
|
||||
<button className="btn btn-primary continue" style={{
|
||||
backgroundColor: "#fda417",
|
||||
border: "#fda417",
|
||||
}} onClick={handleContinue}>Continue</button>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 'shipping' && (
|
||||
<div role="tabpanel" className="tab-pane active">
|
||||
<h3>Investment Details</h3>
|
||||
<p>Investment Details Form</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",
|
||||
}} onClick={handleContinue}>Continue</button>
|
||||
</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>
|
||||
);
|
||||
|
||||
|
||||
const handlePlaceOrder = (e) => {
|
||||
e.preventDefault(); // Prevent form from refreshing the page
|
||||
dispatch(createProperty(formData));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container tabs-wrap">
|
||||
|
||||
|
||||
<form onSubmit={handlePlaceOrder}>
|
||||
<div className="tab-content">
|
||||
|
||||
<div role="tabpanel" className="tab-pane active">
|
||||
<h3>Submit the Property Details</h3>
|
||||
<br />
|
||||
<div className="row gy-3 overflow-hidden">
|
||||
<div className="col-12">
|
||||
<select
|
||||
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 className="col-12">
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
name="title"
|
||||
placeholder="Property title"
|
||||
value={formData.title}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
<label htmlFor="title" className="form-label">
|
||||
Property Title
|
||||
</label>
|
||||
</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 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>
|
||||
);
|
||||
};
|
||||
|
||||
export default Addproperty;
|
||||
|
|
|
@ -19,4 +19,5 @@ API.interceptors.request.use((req) => {
|
|||
export const signUp = (formData) => API.post("/users/signup", 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 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 AuthReducer from "./features/authSlice";
|
||||
import userReducer from "./features/userSlice";
|
||||
import propertyReducer from "./features/propertySlice";
|
||||
|
||||
export default configureStore({
|
||||
reducer: {
|
||||
auth: AuthReducer,
|
||||
user: userReducer,
|
||||
property: propertyReducer,
|
||||
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ export default defineConfig({
|
|||
|
||||
'/users/signup': '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/:id/verify/:token': 'http://67.225.129.127:3002/',
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue