This commit is contained in:
omkieit 2024-09-12 13:41:07 +05:30
parent a7727d10b7
commit 5f9f99b7a0
15 changed files with 293 additions and 88 deletions

View File

@ -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" });
}
};

View File

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

30
ef-api/middleware/auth.js Normal file
View File

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

27
ef-api/models/property.js Normal file
View File

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

View File

@ -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",

View File

@ -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"
}
}

10
ef-api/routes/property.js Normal file
View File

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

View File

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

View File

@ -41,6 +41,7 @@ const App = () => {
</Routes>
</BrowserRouter>
)

View File

@ -1,74 +1,121 @@
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 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 (
<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 &amp; Document</a>
</li>
</ul>
<form onSubmit={handlePlaceOrder}>
<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>
<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>
)}
{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 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>
)}
{activeTab === 'review' && (
<div role="tabpanel" className="tab-pane active">
<h3>Review &amp; Document</h3>
<p>Review &amp; 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 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>
);
};

View File

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

View File

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

View File

@ -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,
},
});

View File

@ -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/',
},