Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
fe55abc83e
|
|||
| 6df9befdd1 | |||
| 0048e9f462 | |||
| dbbc0ce5f2 | |||
| 79c6c80636 | |||
| 9ac0294974 | |||
| 870185001d | |||
| 34bdbf9f9c | |||
| a9d63abf9b | |||
| 53b1e40d8f | |||
| c113a34f1f | |||
| 9e04a4b734 | |||
|
cc9c63d879
|
|||
|
4e6ce0e723
|
|||
|
7da2809399
|
|||
|
3f9e6d3dee
|
|||
| 699bcb9d2c | |||
| 51eb7d8f47 | |||
| 54d90519cb | |||
| d48736114b | |||
| 36911b5701 | |||
| fcc52ed62a | |||
| 29e2d49ef6 | |||
| 92b647fa0e | |||
| 7b2abd29b9 | |||
| 7f96da1555 | |||
| ac723810c5 | |||
| 04317cb9e9 | |||
| 7edab16bd3 | |||
| 86189b0f5b | |||
| 297b094de5 | |||
| bcd600c766 | |||
| 9293287566 | |||
| 22533ee75d | |||
| f8e6efb5b3 | |||
| a67e23fa6a | |||
| 9f98ebd4c4 | |||
|
0dbc742f42
|
|||
|
2be6de06d6
|
|||
| 5cde4a3b45 | |||
|
228ce106b2
|
|||
|
928c3fbb12
|
|||
| c4eb3e940e | |||
| 43613b60ce | |||
|
82f457406a
|
|||
| ec2f1a939b | |||
| 490668ff61 | |||
| 21a21303f8 | |||
| 7edeef04f7 | |||
| 793a0867dd | |||
| 4b7501acf5 | |||
| a585ac58f6 | |||
| d16b59edc0 | |||
| 9e6ccc0343 | |||
| 3215da670e | |||
| cbff64906b | |||
| 27955fc264 | |||
| ee59400d76 | |||
| 88d229cb2d | |||
| 79a29ad982 | |||
| e98b39a9da | |||
| f41d33625f | |||
| ae9108769c | |||
| 6d0813c8db | |||
| 1c7c9425bc | |||
| 341a3a127b | |||
| 66a6ac22d0 | |||
| 466d0b7abe | |||
| 07d691ad4e | |||
| 89af132577 | |||
| 8d9339cdea | |||
| 0a43591739 | |||
| 762e246ccd | |||
| ddf28d9a46 | |||
| 586fd41133 | |||
| 982fbba413 | |||
| b805b8e34e | |||
| 956fb5604a | |||
| 6dc57b28b1 | |||
| fdee1beb05 | |||
| 58cd02b3a5 | |||
| bf03fa12f6 | |||
| 4897c53259 | |||
| c41d788328 | |||
| d5086e21ae | |||
| 355f13b33d | |||
| 68f3efc128 | |||
| 8db0eeba9a | |||
| 439dbe51b6 | |||
| 330589bdf1 | |||
| 8ad01a5c50 | |||
| d9c299a58f | |||
| f7cb1af2c4 | |||
| 1fdb739950 | |||
|
8438a9a2b7
|
|||
| 357071b967 | |||
|
75f31b5b93
|
|||
| fe2cd31200 | |||
| 793e6c2009 | |||
| 15593af2a9 | |||
|
33b5c9ccc8
|
|||
|
e33f856f21
|
|||
|
8f13ac6d4b
|
@@ -0,0 +1,57 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Static files
|
||||||
|
public/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# Database files
|
||||||
|
*.sqlite3
|
||||||
|
*.db
|
||||||
|
*.db-wal
|
||||||
|
*.db-shm
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Other
|
||||||
|
*.log.*
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
const Crop = require("../Models/crop.model.js");
|
||||||
|
const Farm = require("../Models/farm.model.js");
|
||||||
|
const { uploadOnCloudinary } = require("../Utils/cloudinary.js");
|
||||||
|
const { run } = require("../Utils/model.js");
|
||||||
|
|
||||||
|
// Create a new crop
|
||||||
|
const createCrop = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name, farm, harvestDate, growthStage, healthStatus } = req.body;
|
||||||
|
if (!req.file.path) {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Avatar not uploaded on cloudinary.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageUrl = await uploadOnCloudinary(req.file.path);
|
||||||
|
|
||||||
|
console.log("Image url is : ", imageUrl);
|
||||||
|
|
||||||
|
if (!imageUrl) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Image not uploaded on cloudinary.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the farm exists
|
||||||
|
const existingFarm = await Farm.findById(farm);
|
||||||
|
if (!existingFarm)
|
||||||
|
return res.status(404).json({ message: "Farm not found" });
|
||||||
|
|
||||||
|
const crop = new Crop({
|
||||||
|
name,
|
||||||
|
farm,
|
||||||
|
image: imageUrl,
|
||||||
|
harvestDate,
|
||||||
|
growthStage,
|
||||||
|
healthStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
await crop.save();
|
||||||
|
|
||||||
|
// Add crop to farm
|
||||||
|
existingFarm.crops.push(crop._id);
|
||||||
|
await existingFarm.save();
|
||||||
|
|
||||||
|
res.status(201).json(crop);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all crops for a specific farm
|
||||||
|
const getCropsByFarm = async (req, res) => {
|
||||||
|
try {
|
||||||
|
console.log("My farm id is : ", req.params.farmId);
|
||||||
|
const crops = await Crop.find({ farm: req.params.farmId });
|
||||||
|
|
||||||
|
res.status(200).json(crops);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get a single crop by ID
|
||||||
|
const getCropById = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const crop = await Crop.findById(req.params.cropId).populate("farm");
|
||||||
|
|
||||||
|
if (!crop) return res.status(404).json({ message: "Crop not found" });
|
||||||
|
|
||||||
|
res.status(200).json(crop);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update crop details
|
||||||
|
const updateCrop = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const updatedCrop = await Crop.findByIdAndUpdate(
|
||||||
|
req.params.cropId,
|
||||||
|
req.body,
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updatedCrop)
|
||||||
|
return res.status(404).json({ message: "Crop not found" });
|
||||||
|
|
||||||
|
res.status(200).json(updatedCrop);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete a crop
|
||||||
|
const deleteCrop = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const crop = await Crop.findById(req.params.cropId);
|
||||||
|
|
||||||
|
if (!crop) return res.status(404).json({ message: "Crop not found" });
|
||||||
|
|
||||||
|
await crop.deleteOne();
|
||||||
|
|
||||||
|
// Remove crop from the farm
|
||||||
|
await Farm.findByIdAndUpdate(crop.farm, { $pull: { crops: crop._id } });
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Crop deleted successfully" });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update crop growth stage
|
||||||
|
const updateGrowthStage = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { growthStage } = req.body;
|
||||||
|
|
||||||
|
const crop = await Crop.findById(req.params.cropId);
|
||||||
|
if (!crop) return res.status(404).json({ message: "Crop not found" });
|
||||||
|
|
||||||
|
crop.growthStage = growthStage;
|
||||||
|
await crop.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Growth stage updated", crop });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update crop health status
|
||||||
|
const updateHealthStatus = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { healthStatus } = req.body;
|
||||||
|
|
||||||
|
const crop = await Crop.findById(req.params.cropId);
|
||||||
|
if (!crop) return res.status(404).json({ message: "Crop not found" });
|
||||||
|
|
||||||
|
crop.healthStatus = healthStatus;
|
||||||
|
await crop.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Health status updated", crop });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cropHarvest = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const crop = await Crop.findById(req.params.cropId).populate("farm");
|
||||||
|
console.log(crop);
|
||||||
|
|
||||||
|
const message = `My crop is ${crop.name}, given that I have planted it on ${crop.plantedDate}, suggest me a date as to when it will be harvested. Give output in the form: ${crop.name} will take .. months to harvest around (give month name and year).`; // for harvest expectation
|
||||||
|
|
||||||
|
const result = await run(message);
|
||||||
|
res.status(200).json({ message: result });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const suggestNextCrop = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const crop = await Crop.findById(req.params.cropId).populate("farm");
|
||||||
|
console.log(crop);
|
||||||
|
|
||||||
|
const message = `Currently I have ${crop.name} in my field. Considering the best optimal time for its harvestation give my location, ${crop.farm.location} and water content of my field is ${crop.farm.waterContent}. Suggest next crop I should grow and give more suggestions around it. Don't tell me when to harvest ${crop.name}, only tell me next best crop to plant in my field and give suggestions around it.`; // for next sowing suggestion
|
||||||
|
|
||||||
|
const result = await run(message);
|
||||||
|
res.status(200).json({ message: result });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const suggestPesticides = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const crop = await Crop.findById(req.params.cropId).populate("farm");
|
||||||
|
console.log(crop);
|
||||||
|
|
||||||
|
const message = `Considering I have grown ${crop.name} in my field located in ${crop.farm.location} and has water content of ${crop.farm.waterContent}. Suggest pesticides I should use on the crop and when to use it considering my sow date is ${crop.sowDate}. Give precautionary measures and suggestions around it.`; // for pesticides
|
||||||
|
|
||||||
|
const result = await run(message);
|
||||||
|
res.status(200).json({ message: result });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const suggestFertilizers = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const crop = await Crop.findById(req.params.cropId).populate("farm");
|
||||||
|
console.log(crop);
|
||||||
|
|
||||||
|
const message = `Considering I have grown ${crop.name} in my field located in ${crop.farm.location} and has water content of ${crop.farm.waterContent}. Suggest fertilizers I should use on the crop and when to use it, i.e. after how many months after sowing considering sowing date is ${crop.sowDate} considering my sow date is ${crop.sowDate}. Give me precautionary measures.`; // for fertilizers
|
||||||
|
|
||||||
|
const result = await run(message);
|
||||||
|
res.status(200).json({ message: result });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createCrop,
|
||||||
|
getCropsByFarm,
|
||||||
|
getCropById,
|
||||||
|
updateCrop,
|
||||||
|
deleteCrop,
|
||||||
|
updateGrowthStage,
|
||||||
|
updateHealthStatus,
|
||||||
|
cropHarvest,
|
||||||
|
suggestNextCrop,
|
||||||
|
suggestPesticides,
|
||||||
|
suggestFertilizers,
|
||||||
|
};
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
const Farm = require("../Models/farm.model.js");
|
||||||
|
const Crop = require("../Models/crop.model.js");
|
||||||
|
const Finance = require("../Models/finance.model.js");
|
||||||
|
|
||||||
|
// Create a farm
|
||||||
|
const createFarm = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name, location, waterContent, soilType, size } = req.body;
|
||||||
|
|
||||||
|
const farm = new Farm({
|
||||||
|
name,
|
||||||
|
location,
|
||||||
|
size,
|
||||||
|
waterContent,
|
||||||
|
soilType,
|
||||||
|
owner: req.user._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await farm.save();
|
||||||
|
res.status(201).json(farm);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all farms for a user
|
||||||
|
const getUserFarms = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const farms = await Farm.find({ owner: req.user._id })
|
||||||
|
.populate("crops")
|
||||||
|
.populate("finances");
|
||||||
|
|
||||||
|
res.status(200).json(farms);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get a single farm by ID
|
||||||
|
const getFarmById = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const farm = await Farm.findById(req.params.farmId)
|
||||||
|
.populate("crops")
|
||||||
|
.populate("finances");
|
||||||
|
|
||||||
|
if (!farm) return res.status(404).json({ message: "Farm not found" });
|
||||||
|
|
||||||
|
res.status(200).json(farm);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update farm details
|
||||||
|
const updateFarm = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const updatedFarm = await Farm.findByIdAndUpdate(
|
||||||
|
req.params.farmId,
|
||||||
|
req.body,
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updatedFarm)
|
||||||
|
return res.status(404).json({ message: "Farm not found" });
|
||||||
|
|
||||||
|
res.status(200).json(updatedFarm);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete a farm
|
||||||
|
const deleteFarm = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const farm = await Farm.findById(req.params.farmId);
|
||||||
|
|
||||||
|
if (!farm) return res.status(404).json({ message: "Farm not found" });
|
||||||
|
|
||||||
|
await Crop.deleteMany({ farm: farm._id });
|
||||||
|
await Finance.findByIdAndDelete(farm.finances);
|
||||||
|
await farm.deleteOne();
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Farm deleted successfully" });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add fertilizer to a farm
|
||||||
|
const addFertilizer = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name, quantity } = req.body;
|
||||||
|
|
||||||
|
const farm = await Farm.findById(req.params.farmId);
|
||||||
|
if (!farm) return res.status(404).json({ message: "Farm not found" });
|
||||||
|
|
||||||
|
farm.fertilizer.push({ name, quantity });
|
||||||
|
await farm.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Fertilizer added", farm });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add pesticide to a farm
|
||||||
|
const addPesticide = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name, quantity } = req.body;
|
||||||
|
|
||||||
|
const farm = await Farm.findById(req.params.farmId);
|
||||||
|
if (!farm) return res.status(404).json({ message: "Farm not found" });
|
||||||
|
|
||||||
|
farm.pestisides.push({ name, quantity });
|
||||||
|
await farm.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Pesticide added", farm });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createFarm,
|
||||||
|
getUserFarms,
|
||||||
|
getFarmById,
|
||||||
|
updateFarm,
|
||||||
|
deleteFarm,
|
||||||
|
addFertilizer,
|
||||||
|
addPesticide,
|
||||||
|
};
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
const Finance = require("../Models/finance.model.js");
|
||||||
|
const Farm = require("../Models/farm.model.js");
|
||||||
|
|
||||||
|
// Create finance record for a farm
|
||||||
|
const createFinance = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { farm } = req.body;
|
||||||
|
|
||||||
|
// Check if the farm exists
|
||||||
|
const existingFarm = await Farm.findById(farm);
|
||||||
|
if (!existingFarm)
|
||||||
|
return res.status(404).json({ message: "Farm not found" });
|
||||||
|
|
||||||
|
const finance = new Finance({
|
||||||
|
farm,
|
||||||
|
transactions: [],
|
||||||
|
totalExpenses: 0,
|
||||||
|
totalRevenue: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
await finance.save();
|
||||||
|
|
||||||
|
// Link finance to farm
|
||||||
|
existingFarm.finances = finance._id;
|
||||||
|
await existingFarm.save();
|
||||||
|
|
||||||
|
res.status(201).json(finance);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get finance details by farm ID
|
||||||
|
const getFinanceByFarm = async (req, res) => {
|
||||||
|
try {
|
||||||
|
console.log("My farm id is : ", req.params.farmId);
|
||||||
|
const finance = await Finance.findOne({ farm: req.params.farmId });
|
||||||
|
|
||||||
|
if (!finance)
|
||||||
|
return res.status(404).json({ message: "Finance record not found" });
|
||||||
|
|
||||||
|
res.status(200).json(finance);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a transaction (expense/revenue)
|
||||||
|
const addTransaction = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { type, amount, description } = req.body;
|
||||||
|
|
||||||
|
const finance = await Finance.findById(req.params.financeId);
|
||||||
|
if (!finance)
|
||||||
|
return res.status(404).json({ message: "Finance record not found" });
|
||||||
|
|
||||||
|
finance.transactions.push({ type, amount, description });
|
||||||
|
|
||||||
|
// Update totals
|
||||||
|
if (type === "Expense") {
|
||||||
|
finance.totalExpenses += amount;
|
||||||
|
} else if (type === "Revenue") {
|
||||||
|
finance.totalRevenue += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
await finance.save();
|
||||||
|
res.status(200).json({ message: "Transaction added", finance });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete a transaction
|
||||||
|
const deleteTransaction = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const finance = await Finance.findById(req.params.financeId);
|
||||||
|
if (!finance)
|
||||||
|
return res.status(404).json({ message: "Finance record not found" });
|
||||||
|
|
||||||
|
const transaction = finance.transactions.id(req.params.transactionId);
|
||||||
|
if (!transaction)
|
||||||
|
return res.status(404).json({ message: "Transaction not found" });
|
||||||
|
|
||||||
|
// Adjust totals before removing
|
||||||
|
if (transaction.type === "Expense") {
|
||||||
|
finance.totalExpenses -= transaction.amount;
|
||||||
|
} else if (transaction.type === "Revenue") {
|
||||||
|
finance.totalRevenue -= transaction.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.remove();
|
||||||
|
await finance.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Transaction deleted", finance });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all transactions for a farm's finance
|
||||||
|
const getTransactions = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const finance = await Finance.findById(req.params.financeId);
|
||||||
|
if (!finance)
|
||||||
|
return res.status(404).json({ message: "Finance record not found" });
|
||||||
|
|
||||||
|
res.status(200).json(finance.transactions);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get total expenses and revenue
|
||||||
|
const getFinancialSummary = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const finance = await Finance.findById(req.params.financeId);
|
||||||
|
if (!finance)
|
||||||
|
return res.status(404).json({ message: "Finance record not found" });
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
totalExpenses: finance.totalExpenses,
|
||||||
|
totalRevenue: finance.totalRevenue,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createFinance,
|
||||||
|
getFinanceByFarm,
|
||||||
|
addTransaction,
|
||||||
|
deleteTransaction,
|
||||||
|
getTransactions,
|
||||||
|
getFinancialSummary,
|
||||||
|
};
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
const Task = require("../Models/task.model.js");
|
||||||
|
const Farm = require("../Models/farm.model.js");
|
||||||
|
const Crop = require("../Models/crop.model.js");
|
||||||
|
|
||||||
|
// Create a new task
|
||||||
|
const createTask = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { farm, crop, taskType, description, assignedDate, status } =
|
||||||
|
req.body;
|
||||||
|
|
||||||
|
// Validate farm existence
|
||||||
|
const existingFarm = await Farm.findById(farm);
|
||||||
|
if (!existingFarm)
|
||||||
|
return res.status(404).json({ message: "Farm not found" });
|
||||||
|
|
||||||
|
// Validate crop if provided
|
||||||
|
if (crop) {
|
||||||
|
const existingCrop = await Crop.findById(crop);
|
||||||
|
if (!existingCrop)
|
||||||
|
return res.status(404).json({ message: "Crop not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = new Task({
|
||||||
|
farm,
|
||||||
|
crop,
|
||||||
|
taskType,
|
||||||
|
description,
|
||||||
|
assignedDate,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
|
||||||
|
await task.save();
|
||||||
|
res.status(201).json(task);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all tasks for a specific farm
|
||||||
|
const getTasksByFarm = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tasks = await Task.find({ farm: req.params.farmId }).populate("crop");
|
||||||
|
|
||||||
|
res.status(200).json(tasks);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get a single task by ID
|
||||||
|
const getTaskById = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const task = await Task.findById(req.params.taskId).populate("farm crop");
|
||||||
|
|
||||||
|
if (!task) return res.status(404).json({ message: "Task not found" });
|
||||||
|
|
||||||
|
res.status(200).json(task);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update task details
|
||||||
|
const updateTask = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const updatedTask = await Task.findByIdAndUpdate(
|
||||||
|
req.params.taskId,
|
||||||
|
req.body,
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updatedTask)
|
||||||
|
return res.status(404).json({ message: "Task not found" });
|
||||||
|
|
||||||
|
res.status(200).json(updatedTask);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete a task
|
||||||
|
const deleteTask = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const task = await Task.findById(req.params.taskId);
|
||||||
|
|
||||||
|
if (!task) return res.status(404).json({ message: "Task not found" });
|
||||||
|
|
||||||
|
await task.deleteOne();
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Task deleted successfully" });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update task status (Pending → Completed)
|
||||||
|
const updateTaskStatus = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { status } = req.body;
|
||||||
|
|
||||||
|
const task = await Task.findById(req.params.taskId);
|
||||||
|
if (!task) return res.status(404).json({ message: "Task not found" });
|
||||||
|
|
||||||
|
task.status = status;
|
||||||
|
await task.save();
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Task status updated", task });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createTask,
|
||||||
|
getTasksByFarm,
|
||||||
|
getTaskById,
|
||||||
|
updateTask,
|
||||||
|
deleteTask,
|
||||||
|
updateTaskStatus,
|
||||||
|
};
|
||||||
@@ -0,0 +1,466 @@
|
|||||||
|
const catchAsyncErrors = require("../Middlewares/catchAsyncErrors.js");
|
||||||
|
const User = require("../Models/user.model.js");
|
||||||
|
const { uploadOnCloudinary } = require("../Utils/cloudinary.js");
|
||||||
|
const sendEmail = require("../Utils/sendmail.js");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
|
||||||
|
// Register or Sign up new User -- Done
|
||||||
|
const registerUser = catchAsyncErrors(async (req, res) => {
|
||||||
|
const { name, email, password, role } = req.body;
|
||||||
|
|
||||||
|
const user = await User.create({
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
role,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "User not created something went wrong.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "User is registered successfully",
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login user in our web app -- Done
|
||||||
|
const loginUser = catchAsyncErrors(async (req, res) => {
|
||||||
|
const { email, password } = req.body;
|
||||||
|
|
||||||
|
const user = await User.findOne({ email });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "User not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkUser = await user.isPasswordCorrect(password);
|
||||||
|
|
||||||
|
if (!checkUser) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Password is incorrect",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await user.generateRefreshToken();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "token not created something went wrong.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
user.password = null;
|
||||||
|
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.cookie(process.env.TOKEN_NAME, token, {
|
||||||
|
path: "/",
|
||||||
|
sameSite: "None",
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
httpOnly: true,
|
||||||
|
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||||
|
})
|
||||||
|
.json({
|
||||||
|
success: true,
|
||||||
|
message: "User is successfully logged in.",
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logout user in our web app -- Done
|
||||||
|
const logoutUser = catchAsyncErrors(async (req, res) => {
|
||||||
|
return res
|
||||||
|
.clearCookie(process.env.TOKEN_NAME, {
|
||||||
|
path: "/",
|
||||||
|
sameSite: "None",
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
httpOnly: true,
|
||||||
|
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||||
|
})
|
||||||
|
.status(201)
|
||||||
|
.json({
|
||||||
|
success: true,
|
||||||
|
message: "User is logged out successfully",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// -- DONE
|
||||||
|
const intializeUser = catchAsyncErrors(async (req, res) => {
|
||||||
|
const tokenValue = req.cookies[process.env.TOKEN_NAME];
|
||||||
|
|
||||||
|
// console.log("I am the one who is doing this : ", tokenValue);
|
||||||
|
|
||||||
|
if (!tokenValue) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "User is not logged in.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = await jwt.verify(
|
||||||
|
tokenValue,
|
||||||
|
process.env.REFRESH_TOKEN_SECRET
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "Something went wrong",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findById(payload._id).select("-password");
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "User data get successfully",
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "Something went wrong",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update user deatails -- ADMIN
|
||||||
|
const updateUserDetails = catchAsyncErrors(async (req, res) => {
|
||||||
|
const user = await User.findById(req.params.id);
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: true,
|
||||||
|
message: "User not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, email } = req.body;
|
||||||
|
|
||||||
|
const updateUser = await User.findByIdAndUpdate(req.params.id, {
|
||||||
|
$set: {
|
||||||
|
name: name,
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
}).select("-password");
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "User is updated successfully",
|
||||||
|
data: updateUser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// forget password -- Done
|
||||||
|
const forgetPassword = catchAsyncErrors(async (req, res) => {
|
||||||
|
const { email } = req.body;
|
||||||
|
const user = await User.findOne({ email });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "User not found ",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get reset password
|
||||||
|
|
||||||
|
const resetToken = await user.getResetPassword();
|
||||||
|
|
||||||
|
await user.save({ validateBeforeSave: false });
|
||||||
|
|
||||||
|
/*const resetPasswordUrl = `${req.protocol}://${req.get(
|
||||||
|
"host"
|
||||||
|
)}/api/v1/password/reset/${resetToken}`;*/
|
||||||
|
|
||||||
|
const resetPasswordUrl = `${process.env.FRONTEND_URI}/user/api/v1/password/reset/${resetToken}`;
|
||||||
|
|
||||||
|
const message = `Your password token is :-\n\n${resetPasswordUrl}\n\nIf you are not requested this email then please ingore this mail.`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendEmail({
|
||||||
|
email: user.email,
|
||||||
|
subject: "MentorFlux password recovery",
|
||||||
|
message: message,
|
||||||
|
});
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: `Email sent to ${email} successfully`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
user.resetPasswordToken = undefined;
|
||||||
|
user.resetPasswordExpiry = undefined;
|
||||||
|
await user.save({ validateBeforeSave: false });
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Something went wrong ",
|
||||||
|
error: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// reset users password -- DONE
|
||||||
|
const resetPassword = catchAsyncErrors(async (req, res) => {
|
||||||
|
const token = req.params.token;
|
||||||
|
|
||||||
|
const { password, confirmPassword } = req.body;
|
||||||
|
|
||||||
|
//console.log("My password is :", password);
|
||||||
|
//console.log("My confirmPassword is :", confirmPassword);
|
||||||
|
//console.log("My token is :", token);
|
||||||
|
const resetPasswordToken = await crypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(token)
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
resetPasswordToken,
|
||||||
|
resetPasswordExpiry: { $gte: Date.now() },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: "Reset Password token is invalid or has been expired",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: "Please enter password and confirm password",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
user.password = password;
|
||||||
|
user.resetPasswordToken = undefined;
|
||||||
|
user.resetPasswordExpiry = undefined;
|
||||||
|
|
||||||
|
//console.log("To check the user ", user);
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "Password changed successfully",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// get user personal details
|
||||||
|
const getUserDetails = catchAsyncErrors(async (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
|
const user = await User.findById(req.user._id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Something went wrong ",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "User details are fetched successfully",
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update users password
|
||||||
|
const updatePassword = catchAsyncErrors(async (req, res) => {
|
||||||
|
const { password, oldPassword, confirmPassword } = req.body;
|
||||||
|
|
||||||
|
const user = await User.findById(req.user._id);
|
||||||
|
|
||||||
|
const isPasswordMatched = await user.isPasswordCorrect(oldPassword);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "User not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPasswordMatched) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Old password is incorrect.Please enter correct password ",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Password and Confirm password should be same.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
user.password = password;
|
||||||
|
await user.save({ validateBeforeSave: false });
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "Password upadated successfully",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// update personal details
|
||||||
|
const updatePersonalDetails = catchAsyncErrors(async (req, res) => {
|
||||||
|
const { name, email } = req.body;
|
||||||
|
const user = await User.findByIdAndUpdate(req.user._id, {
|
||||||
|
$set: {
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Something went wrong",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "User details updated successfully",
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all users details -- ADMIN
|
||||||
|
const getAllusersDetail = catchAsyncErrors(async (req, res) => {
|
||||||
|
const users = await find();
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "All user fetch successfully",
|
||||||
|
data: users,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// get single user details
|
||||||
|
const getSingaluserDetail = catchAsyncErrors(async (req, res) => {
|
||||||
|
const user = await User.findById(req.params.id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "User not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// upadate user Role -- ADMIN
|
||||||
|
const updateUserRole = catchAsyncErrors(async (req, res) => {
|
||||||
|
const { name, email, role } = req.body;
|
||||||
|
const user = await User.findByIdAndUpdate(req.params.id, {
|
||||||
|
$set: {
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
role,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Something went wrong",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "User Role updated successfully",
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete user -- ADMIN
|
||||||
|
const DeleteUser = catchAsyncErrors(async (req, res) => {
|
||||||
|
const user = await User.findByIdAndDelete(req.params.id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "User does not exist",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "User deleted successfully",
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// update avatar -- user
|
||||||
|
const updateAvatar = catchAsyncErrors(async (req, res) => {
|
||||||
|
//console.log("Our file is : ", req.file.path);
|
||||||
|
|
||||||
|
if (!req.file.path) {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Avatar not uploaded on cloudinary.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarUrl = await uploadOnCloudinary(req.file.path);
|
||||||
|
|
||||||
|
if (!avatarUrl) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Avatar not uploaded on cloudinary.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log("Avatar url is : ", avatarUrl);
|
||||||
|
|
||||||
|
//console.log("our user is : ", req.user);
|
||||||
|
|
||||||
|
const user = await User.findByIdAndUpdate(req.user._id, {
|
||||||
|
$set: {
|
||||||
|
avatar: avatarUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "User not found.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "Avatar updated successfully.",
|
||||||
|
data: user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
registerUser,
|
||||||
|
loginUser,
|
||||||
|
logoutUser,
|
||||||
|
updateUserDetails,
|
||||||
|
forgetPassword,
|
||||||
|
resetPassword,
|
||||||
|
getUserDetails,
|
||||||
|
updatePassword,
|
||||||
|
updatePersonalDetails,
|
||||||
|
updateUserRole,
|
||||||
|
DeleteUser,
|
||||||
|
intializeUser,
|
||||||
|
updateAvatar,
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const mongoose = require("mongoose");
|
||||||
|
const catchAsyncErrors = require("../Middlewares/catchAsyncErrors.js");
|
||||||
|
|
||||||
|
const DB_connect = catchAsyncErrors(async () => {
|
||||||
|
try {
|
||||||
|
const connectionInstance = await mongoose.connect(
|
||||||
|
`${process.env.MONGODB_URI}/${process.env.DATABASE_NAME}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!connectionInstance) {
|
||||||
|
console.log("MongoDB connection failed");
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
"MongoDB connected Successfully on server : " +
|
||||||
|
connectionInstance.connection.host
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("MongoDB connection failed due to some error :", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
module.exports = DB_connect;
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
const User = require("../Models/user.model.js");
|
||||||
|
const dotenv = require("dotenv");
|
||||||
|
dotenv.config({
|
||||||
|
path: "./.env",
|
||||||
|
});
|
||||||
|
|
||||||
|
async function checkAuthenticated(req, res, next) {
|
||||||
|
const tokenValue = req.cookies[process.env.TOKEN_NAME];
|
||||||
|
|
||||||
|
console.log("I am called", tokenValue);
|
||||||
|
if (!tokenValue) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "User is not logged in.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const payload = await jwt.verify(
|
||||||
|
tokenValue,
|
||||||
|
process.env.REFRESH_TOKEN_SECRET
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
req.user = payload;
|
||||||
|
return next();
|
||||||
|
} catch (error) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function authorizeRoles(...roles) {
|
||||||
|
return async (req, res, next) => {
|
||||||
|
if (!roles.includes(req.user.role)) {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: "You are unauthorised to access this resource",
|
||||||
|
});
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { checkAuthenticated, authorizeRoles };
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (theFunc) => (req, res, next) => {
|
||||||
|
Promise.resolve(theFunc(req, res, next)).catch(next);
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const multer = require("multer");
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, "./public/images");
|
||||||
|
},
|
||||||
|
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
const uniquePrefix = Date.now();
|
||||||
|
cb(null, uniquePrefix + "-" + file.originalname);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload = multer({ storage: storage });
|
||||||
|
|
||||||
|
module.exports = upload;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const cropSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
name: { type: String, required: true },
|
||||||
|
farm: { type: mongoose.Schema.Types.ObjectId, ref: "Farm", required: true },
|
||||||
|
image: { type: String },
|
||||||
|
plantedDate: { type: Date, required: true, default: Date.now() },
|
||||||
|
harvestDate: { type: Date },
|
||||||
|
growthStage: {
|
||||||
|
type: String,
|
||||||
|
enum: ["Planted", "Growing", "Ready to Harvest"],
|
||||||
|
default: "Planted",
|
||||||
|
},
|
||||||
|
healthStatus: { type: String, default: "Healthy" },
|
||||||
|
},
|
||||||
|
{ timestamps: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const Crop = mongoose.model("Crop", cropSchema);
|
||||||
|
|
||||||
|
module.exports = Crop;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const farmSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
name: { type: String, required: true },
|
||||||
|
location: { type: String, required: true },
|
||||||
|
owner: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: "User",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
size: { type: String },
|
||||||
|
waterContent: { type: String, required: true },
|
||||||
|
soilType: { type: String, required: true },
|
||||||
|
fertilizer: [
|
||||||
|
{
|
||||||
|
name: { type: String },
|
||||||
|
quantity: { type: Number },
|
||||||
|
addedAt: { type: Date, default: Date.now },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pestisides: [
|
||||||
|
{
|
||||||
|
name: { type: String },
|
||||||
|
quantity: { type: Number },
|
||||||
|
addedAt: { type: Date, default: Date.now },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
crops: [{ type: mongoose.Schema.Types.ObjectId, ref: "Crop" }],
|
||||||
|
finances: { type: mongoose.Schema.Types.ObjectId, ref: "Finance" },
|
||||||
|
},
|
||||||
|
{ timestamps: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const Farm = mongoose.model("Farm", farmSchema);
|
||||||
|
|
||||||
|
module.exports = Farm;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const mongoose = require("mongoose");
|
||||||
|
const financeSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
farm: { type: mongoose.Schema.Types.ObjectId, ref: "Farm", required: true },
|
||||||
|
transactions: [
|
||||||
|
{
|
||||||
|
type: { type: String, enum: ["Expense", "Revenue"], required: true },
|
||||||
|
amount: { type: Number, required: true },
|
||||||
|
description: { type: String },
|
||||||
|
date: { type: Date, default: Date.now },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
totalExpenses: { type: Number, default: 0 },
|
||||||
|
totalRevenue: { type: Number, default: 0 },
|
||||||
|
},
|
||||||
|
{ timestamps: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const Finance = mongoose.model("Finance", financeSchema);
|
||||||
|
|
||||||
|
module.exports = Finance;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const taskSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
farm: { type: mongoose.Schema.Types.ObjectId, ref: "Farm", required: true },
|
||||||
|
crop: { type: mongoose.Schema.Types.ObjectId, ref: "Crop" },
|
||||||
|
taskType: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
"Sowing",
|
||||||
|
"Watering",
|
||||||
|
"Fertilization",
|
||||||
|
"Pest Control",
|
||||||
|
"Harvesting",
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: { type: String },
|
||||||
|
assignedDate: { type: Date, required: true, default: Date.now },
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: ["Pending", "Completed"],
|
||||||
|
default: "Pending",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ timestamps: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const Task = mongoose.model("Task", taskSchema);
|
||||||
|
|
||||||
|
module.exports = Task;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
const mongoose = require("mongoose");
|
||||||
|
const bcrypt = require("bcrypt");
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
const userSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: [true, "Please Enter your name"],
|
||||||
|
maxLength: [30, "Please Enter the valid name"],
|
||||||
|
minLength: [2, "Name should have more than 5 characters"],
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
lowerCase: true,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
minLength: [6, "Password should have more than 6 characters"],
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
type: String,
|
||||||
|
default: "/images/profile.jpeg",
|
||||||
|
},
|
||||||
|
role: { type: String, enum: ["farmer", "admin"], default: "farmer" },
|
||||||
|
farms: [{ type: mongoose.Schema.Types.ObjectId, ref: "Farm" }],
|
||||||
|
|
||||||
|
resetPasswordToken: String,
|
||||||
|
resetPasswordExpiry: Date,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
userSchema.pre("save", async function (next) {
|
||||||
|
if (!this.isModified("password")) return next();
|
||||||
|
|
||||||
|
this.password = await bcrypt.hash(this.password, 10);
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
userSchema.methods.isPasswordCorrect = async function (password) {
|
||||||
|
return await bcrypt.compare(password, this.password);
|
||||||
|
};
|
||||||
|
|
||||||
|
userSchema.methods.generateRefreshToken = async function () {
|
||||||
|
return await jwt.sign(
|
||||||
|
{
|
||||||
|
_id: this._id,
|
||||||
|
email: this.email,
|
||||||
|
},
|
||||||
|
process.env.REFRESH_TOKEN_SECRET
|
||||||
|
// {
|
||||||
|
// expiresIn: process.env.REFRESH_TOKEN_EXPIRY,
|
||||||
|
// }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
userSchema.methods.getResetPassword = async function () {
|
||||||
|
// Generating token
|
||||||
|
const resetToken = await crypto.randomBytes(20).toString("hex");
|
||||||
|
|
||||||
|
// Hashing and adding reset password token to userschema
|
||||||
|
|
||||||
|
this.resetPasswordToken = crypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(resetToken)
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
|
this.resetPasswordExpiry = Date.now() + 15 * 60 * 1000;
|
||||||
|
|
||||||
|
return resetToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
const User = mongoose.model("User", userSchema);
|
||||||
|
|
||||||
|
module.exports = User;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const {
|
||||||
|
createCrop,
|
||||||
|
getCropsByFarm,
|
||||||
|
getCropById,
|
||||||
|
updateCrop,
|
||||||
|
deleteCrop,
|
||||||
|
updateHealthStatus,
|
||||||
|
updateGrowthStage,
|
||||||
|
cropHarvest,
|
||||||
|
suggestNextCrop,
|
||||||
|
suggestPesticides,
|
||||||
|
suggestFertilizers,
|
||||||
|
} = require("../Controllers/crop.controller.js");
|
||||||
|
const { checkAuthenticated } = require("../Middlewares/authentication.js");
|
||||||
|
const upload = require("../Middlewares/multer.js");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Routes for crop management
|
||||||
|
router.post("/", checkAuthenticated, upload.single("image"), createCrop); // Create a new crop
|
||||||
|
router.get("/farm/:farmId", checkAuthenticated, getCropsByFarm); // Get all crops
|
||||||
|
router.get("/:cropId", checkAuthenticated, getCropById); // Get a crop by ID
|
||||||
|
router.put("/:cropId", checkAuthenticated, updateCrop); // Update crop details
|
||||||
|
router.delete("/:cropId", checkAuthenticated, deleteCrop); // Delete a crop
|
||||||
|
router.put("/health/:cropId", checkAuthenticated, updateHealthStatus);
|
||||||
|
router.put("/growth/:cropId", checkAuthenticated, updateGrowthStage);
|
||||||
|
|
||||||
|
router.get("/harvest/:cropId", checkAuthenticated, cropHarvest);
|
||||||
|
router.get("/nextCrop/:cropId", checkAuthenticated, suggestNextCrop);
|
||||||
|
router.get("/pesticides/:cropId", checkAuthenticated, suggestPesticides);
|
||||||
|
router.get("/fertilizers/:cropId", checkAuthenticated, suggestFertilizers);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const {
|
||||||
|
createFarm,
|
||||||
|
getUserFarms,
|
||||||
|
getFarmById,
|
||||||
|
updateFarm,
|
||||||
|
deleteFarm,
|
||||||
|
} = require("../Controllers/farm.controller.js");
|
||||||
|
const { checkAuthenticated } = require("../Middlewares/authentication.js");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post("/", checkAuthenticated, createFarm); // Create a new farm
|
||||||
|
router.get("/", checkAuthenticated, getUserFarms); // Get all farms
|
||||||
|
router.get("/:farmId", checkAuthenticated, getFarmById); // Get a farm by ID
|
||||||
|
router.put("/:farmId", checkAuthenticated, updateFarm); // Update a farm
|
||||||
|
router.delete("/:farmId", checkAuthenticated, deleteFarm); // Delete a farm
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const {
|
||||||
|
addTransaction,
|
||||||
|
createFinance,
|
||||||
|
getFinanceByFarm,
|
||||||
|
deleteTransaction,
|
||||||
|
getTransactions,
|
||||||
|
getFinancialSummary,
|
||||||
|
} = require("../Controllers/finance.controller.js");
|
||||||
|
const { checkAuthenticated } = require("../Middlewares/authentication.js");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Routes for finance management
|
||||||
|
router.post("/", checkAuthenticated, createFinance); // Create a new finance record
|
||||||
|
router.get("/:farmId", checkAuthenticated, getFinanceByFarm); // Get all finance records
|
||||||
|
router.get("/transactions/:financeId", checkAuthenticated, getTransactions); // Get a finance record by ID
|
||||||
|
router.get("/summary/:financeId", checkAuthenticated, getFinancialSummary); //
|
||||||
|
router.delete("/:financeId", checkAuthenticated, deleteTransaction); // Delete a finance record
|
||||||
|
|
||||||
|
// Add transactions (Expense/Revenue) to a finance record
|
||||||
|
router.post("/:financeId/transaction", checkAuthenticated, addTransaction);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const { checkAuthenticated } = require("../Middlewares/authentication.js");
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const {
|
||||||
|
createTask,
|
||||||
|
getTasksByFarm,
|
||||||
|
getTaskById,
|
||||||
|
updateTask,
|
||||||
|
deleteTask,
|
||||||
|
updateTaskStatus,
|
||||||
|
} = require("../Controllers/task.controller.js");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Routes for task management
|
||||||
|
router.post("/", checkAuthenticated, createTask); // Create a new task
|
||||||
|
router.get("/farm/:farmId", checkAuthenticated, getTasksByFarm); // Get all tasks for a specific farm
|
||||||
|
router.get("/:taskId", checkAuthenticated, getTaskById); // Get a task by ID
|
||||||
|
router.put("/:taskId", checkAuthenticated, updateTask); // Update task details
|
||||||
|
router.delete("/:taskId", checkAuthenticated, deleteTask); // Delete a task
|
||||||
|
|
||||||
|
// Update task status (Pending → Completed)
|
||||||
|
router.patch("/:taskId/status", checkAuthenticated, updateTaskStatus);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const {
|
||||||
|
registerUser,
|
||||||
|
loginUser,
|
||||||
|
logoutUser,
|
||||||
|
updateUserDetails,
|
||||||
|
forgetPassword,
|
||||||
|
resetPassword,
|
||||||
|
getUserDetails,
|
||||||
|
updatePassword,
|
||||||
|
updatePersonalDetails,
|
||||||
|
DeleteUser,
|
||||||
|
updateUserRole,
|
||||||
|
intializeUser,
|
||||||
|
updateAvatar,
|
||||||
|
} = require("../Controllers/user.controller.js");
|
||||||
|
|
||||||
|
const { checkAuthenticated } = require("../Middlewares/authentication.js");
|
||||||
|
|
||||||
|
const upload = require("../Middlewares/multer.js");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.route("/register").post(registerUser);
|
||||||
|
|
||||||
|
router.route("/login").post(loginUser);
|
||||||
|
|
||||||
|
router.route("/password/forgot").post(forgetPassword);
|
||||||
|
|
||||||
|
router.route("/password/reset/:token").put(resetPassword);
|
||||||
|
|
||||||
|
router.route("/logout").get(logoutUser);
|
||||||
|
|
||||||
|
router.route("/update/:id").put(updateUserDetails);
|
||||||
|
|
||||||
|
router.route("/me").get(checkAuthenticated, getUserDetails);
|
||||||
|
|
||||||
|
router.route("/getuser").get(intializeUser);
|
||||||
|
|
||||||
|
router.route("/password/update").put(updatePassword);
|
||||||
|
|
||||||
|
router.route("/me/update").put(updatePersonalDetails);
|
||||||
|
|
||||||
|
router.route("/user/delete/:id").delete(DeleteUser);
|
||||||
|
|
||||||
|
router.route("/user/updateRole/:id").put(updateUserRole);
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/user/avatar")
|
||||||
|
.put(checkAuthenticated, upload.single("avatar"), updateAvatar);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
const cloudinary = require("cloudinary").v2;
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
const uploadOnCloudinary = async (localFilePath) => {
|
||||||
|
try {
|
||||||
|
if (!localFilePath) return null;
|
||||||
|
|
||||||
|
const responce = await cloudinary.uploader.upload(localFilePath, {
|
||||||
|
resource_type: "auto",
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("File is uploaded successfully");
|
||||||
|
fs.unlinkSync(localFilePath, () => {
|
||||||
|
console.log("file removed successfully");
|
||||||
|
});
|
||||||
|
return responce.url;
|
||||||
|
} catch (error) {
|
||||||
|
fs.unlinkSync(localFilePath);
|
||||||
|
console.log("file removed successfully");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module.exports = { uploadOnCloudinary };
|
||||||
@@ -0,0 +1,456 @@
|
|||||||
|
// const {
|
||||||
|
// GoogleGenerativeAI,
|
||||||
|
// HarmCategory,
|
||||||
|
// HarmBlockThreshold,
|
||||||
|
// } = require("@google/generative-ai");
|
||||||
|
// const {
|
||||||
|
// GoogleGenerativeAI,
|
||||||
|
// HarmCategory,
|
||||||
|
// HarmBlockThreshold,
|
||||||
|
// } = require("@google/generative-ai");
|
||||||
|
// const dotenv = require("dotenv");
|
||||||
|
|
||||||
|
// dotenv.config({
|
||||||
|
// path: "./.env",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const apiKey = process.env.GEMINI_API_KEY;
|
||||||
|
// const genAI = new GoogleGenerativeAI(apiKey);
|
||||||
|
|
||||||
|
// const model = genAI.getGenerativeModel({
|
||||||
|
// model: "gemini-2.0-flash",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const generationConfig = {
|
||||||
|
// temperature: 1,
|
||||||
|
// topP: 0.95,
|
||||||
|
// topK: 40,
|
||||||
|
// maxOutputTokens: 8192,
|
||||||
|
// responseMimeType: "text/plain",
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export async function run(message) {
|
||||||
|
// const chatSession = model.startChat({
|
||||||
|
// generationConfig,
|
||||||
|
// history: [
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "AI Model Guidelines:\n\nContext Restriction: Only provide answers based on the content available on [Your Website Name]. Do not generate responses unrelated to the website's information.\nNo External Knowledge: If a user asks about a topic outside the website’s scope, politely inform them that you can only assist with website-related queries.\nSafe & Ethical Responses: Do not answer harmful, illegal, controversial, or inappropriate questions. If such a query is detected, respond with a neutral message stating that the request cannot be fulfilled.\nAccuracy & Clarity: Ensure responses are factual, clear, and helpful, directly referencing available website data without assumptions or speculation.\n\nGreeting should be similar lto this :\nHello! 👋\nI’m here to assist you with information available on [Your Website Name]. Here’s what I can do:\n\n✅ Answer questions based on the content from this website.\n✅ Guide you through available services, features, and resources.\n✅ Help you navigate and find what you need.\n\nuse emoji when you feel like it makes sense and increases the user understanding",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Hello! 👋\n\nI’m here to assist you with information available on [Your Website Name]. Here’s what I can do:\n\n✅ Answer questions based on the content from this website.\n✅ Guide you through available services, features, and resources.\n✅ Help you navigate and find what you need.\n\nHow can I help you today? 😃\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [{ text: "forget everythings until now " }],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I am now reset and ready to assist you with information available on [Your Website Name], adhering to the guidelines. How can I help you?\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "you will get some json or js object which will be some questions related to farming, you have to answer those questions and give proper description which is neccessary for a farmer to have if you recommend some kind of poisonous pestiside or something give a small warning and some things to keep some precausetions, dont answer anything unrelated to farming and strictly avoid harmful topics like war or viiolence, if farmer wants to kill pestisides then thats ok but anything else is not \n ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will answer questions related to farming based on the content available on [Your Website Name]. I will provide detailed descriptions where necessary, including precautions when recommending potentially harmful pesticides. I will strictly avoid topics unrelated to farming or any harmful subjects. Please provide the JSON or JS object containing the questions. I'm ready! 🚜\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "keep the warning and precuastions short, and you are not a chatbot you are a data processing unit who will have the data and analyze it and answer accordingly\n ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will function as a data processing unit, analyzing the provided data related to farming and answering questions with concise warnings and precautions where applicable. I will not engage in conversational chatbot behavior. Please provide the data. I'm ready to process! 👩🌾\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const result = await chatSession.sendMessage(message);
|
||||||
|
// console.log(result.response.text());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const {
|
||||||
|
// GoogleGenerativeAI,
|
||||||
|
// HarmCategory,
|
||||||
|
// HarmBlockThreshold,
|
||||||
|
// } = require("@google/generative-ai");
|
||||||
|
// const apiKey = "AIzaSyDsXug23r4umgcJpj77KeqNyYW0hQnYDgg";
|
||||||
|
// const genAI = new GoogleGenerativeAI(apiKey);
|
||||||
|
// const model = genAI.getGenerativeModel({
|
||||||
|
// model: "gemini-2.0-flash",
|
||||||
|
// });
|
||||||
|
// const generationConfig = {
|
||||||
|
// temperature: 1,
|
||||||
|
// topP: 0.95,
|
||||||
|
// topK: 40,
|
||||||
|
// maxOutputTokens: 8192,
|
||||||
|
// responseMimeType: "text/plain",
|
||||||
|
// };
|
||||||
|
// async function run(message) {
|
||||||
|
// const chatSession = model.startChat({
|
||||||
|
// generationConfig,
|
||||||
|
// history: [
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "you will get some json or js object which will be some questions related to farming, you have to answer those questions and give proper description which is neccessary for a farmer to have if you recommend some kind of poisonous pestiside or something give a small warning and some things to keep some precausetions, dont answer anything unrelated to farming and strictly avoid harmful topics like war or viiolence, if farmer wants to kill pestisides then thats ok but anything else is not \n ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will answer questions related to farming based on the content available on [Your Website Name]. I will provide detailed descriptions where necessary, including precautions when recommending potentially harmful pesticides. I will strictly avoid topics unrelated to farming or any harmful subjects. Please provide the JSON or JS object containing the questions. I'm ready! 🚜\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "keep the warning and precuastions short, and you are not a chatbot you are a data processing unit who will have the data and analyze it and answer accordingly\n ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will function as a data processing unit, analyzing the provided data related to farming and answering questions with concise warnings and precautions where applicable. I will not engage in conversational chatbot behavior. Please provide the data. I'm ready to process! 👩🌾\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
// const result = await chatSession.sendMessage(message);
|
||||||
|
// console.log(result.response.text());
|
||||||
|
// }
|
||||||
|
// run("How we check the farmes soil type ? ");
|
||||||
|
|
||||||
|
// const {
|
||||||
|
// GoogleGenerativeAI,
|
||||||
|
// HarmCategory,
|
||||||
|
// HarmBlockThreshold,
|
||||||
|
// } = require("@google/generative-ai");
|
||||||
|
|
||||||
|
// const apiKey = "AIzaSyDsXug23r4umgcJpj77KeqNyYW0hQnYDgg";
|
||||||
|
// const genAI = new GoogleGenerativeAI(apiKey);
|
||||||
|
|
||||||
|
// const model = genAI.getGenerativeModel({
|
||||||
|
// model: "gemini-2.0-flash",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const generationConfig = {
|
||||||
|
// temperature: 1,
|
||||||
|
// topP: 0.95,
|
||||||
|
// topK: 40,
|
||||||
|
// maxOutputTokens: 8192,
|
||||||
|
// responseMimeType: "text/plain",
|
||||||
|
// };
|
||||||
|
|
||||||
|
// async function run(message) {
|
||||||
|
// const chatSession = model.startChat({
|
||||||
|
// generationConfig,
|
||||||
|
// history: [
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "AI Guidelines:\n- The AI must only provide answers related to farming, including crops, sowing, irrigation, harvesting, fertilizers, pesticides, soil health, and climate conditions. \n- The AI must not answer anything outside farming. \n- If asked an unrelated question, the AI must not respond. \n- If the query is unclear, the AI must default to farming-related guidance. \n- The AI must provide only one-line responses without extra details. \n- No harmful, hurtful, or controversial responses. \n- No advice on illegal, unsafe, or unethical farming practices.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will only provide one-line responses directly related to farming.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const result = await chatSession.sendMessage(message);
|
||||||
|
// console.log(result.response.text());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const {
|
||||||
|
// GoogleGenerativeAI,
|
||||||
|
// HarmCategory,
|
||||||
|
// HarmBlockThreshold,
|
||||||
|
// } = require("@google/generative-ai");
|
||||||
|
|
||||||
|
// const apiKey = "AIzaSyDsXug23r4umgcJpj77KeqNyYW0hQnYDgg";
|
||||||
|
// const genAI = new GoogleGenerativeAI(apiKey);
|
||||||
|
|
||||||
|
// const model = genAI.getGenerativeModel({
|
||||||
|
// model: "gemini-2.0-flash",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const generationConfig = {
|
||||||
|
// temperature: 1,
|
||||||
|
// topP: 0.95,
|
||||||
|
// topK: 40,
|
||||||
|
// maxOutputTokens: 8192,
|
||||||
|
// responseMimeType: "text/plain",
|
||||||
|
// };
|
||||||
|
|
||||||
|
// async function run() {
|
||||||
|
// const chatSession = model.startChat({
|
||||||
|
// generationConfig,
|
||||||
|
// history: [
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "AI Guidelines:\n- The AI must only provide answers related to farming, including crops, sowing, irrigation, harvesting, fertilizers, pesticides, soil health, and climate conditions. \n- The AI must not answer anything outside farming. \n- If asked an unrelated question, the AI must not respond. \n- If the query is unclear, the AI must default to farming-related guidance. \n- The AI must provide only one-line responses without extra details. \n- No harmful, hurtful, or controversial responses. \n- No advice on illegal, unsafe, or unethical farming practices.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will only provide one-line responses directly related to farming.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "you can also receive json or javascript object as an input ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will process the JSON or JavaScript object only if it pertains to farming and respond with a single line.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const result = await chatSession.sendMessage("INSERT_INPUT_HERE");
|
||||||
|
// console.log(result.response.text());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const {
|
||||||
|
// GoogleGenerativeAI,
|
||||||
|
// HarmCategory,
|
||||||
|
// HarmBlockThreshold,
|
||||||
|
// } = require("@google/generative-ai");
|
||||||
|
|
||||||
|
// const apiKey = "AIzaSyDsXug23r4umgcJpj77KeqNyYW0hQnYDgg";
|
||||||
|
// const genAI = new GoogleGenerativeAI(apiKey);
|
||||||
|
|
||||||
|
// const model = genAI.getGenerativeModel({
|
||||||
|
// model: "gemini-2.0-flash",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const generationConfig = {
|
||||||
|
// temperature: 1,
|
||||||
|
// topP: 0.95,
|
||||||
|
// topK: 40,
|
||||||
|
// maxOutputTokens: 8192,
|
||||||
|
// responseMimeType: "text/plain",
|
||||||
|
// };
|
||||||
|
|
||||||
|
// async function run(message) {
|
||||||
|
// const chatSession = model.startChat({
|
||||||
|
// generationConfig,
|
||||||
|
// history: [
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "AI Guidelines:\n- The AI must only provide answers related to farming, including crops, sowing, irrigation, harvesting, fertilizers, pesticides, soil health, and climate conditions. \n- The AI must not answer anything outside farming. \n- If asked an unrelated question, the AI must not respond. \n- If the query is unclear, the AI must default to farming-related guidance. \n- The AI must provide only one-line responses without extra details. \n- No harmful, hurtful, or controversial responses. \n- No advice on illegal, unsafe, or unethical farming practices.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will only provide one-line responses directly related to farming.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "you can also receive json or javascript object as an input ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will process the JSON or JavaScript object only if it pertains to farming and respond with a single line.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "user",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Answer everything which falls under the farming and agriculture even if the prompt does not include any farm or agriculture word in it",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// role: "model",
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// text: "Understood. I will assume all queries are related to farming and provide a one-line farming-related response.\n",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const result = await chatSession.sendMessage(message);
|
||||||
|
// console.log(result.response.text());
|
||||||
|
|
||||||
|
// return result.response.text();
|
||||||
|
// }
|
||||||
|
|
||||||
|
const {
|
||||||
|
GoogleGenerativeAI,
|
||||||
|
HarmCategory,
|
||||||
|
HarmBlockThreshold,
|
||||||
|
} = require("@google/generative-ai");
|
||||||
|
|
||||||
|
const apiKey = "AIzaSyDsXug23r4umgcJpj77KeqNyYW0hQnYDgg";
|
||||||
|
const genAI = new GoogleGenerativeAI(apiKey);
|
||||||
|
|
||||||
|
const model = genAI.getGenerativeModel({
|
||||||
|
model: "gemini-2.0-flash",
|
||||||
|
});
|
||||||
|
|
||||||
|
const generationConfig = {
|
||||||
|
temperature: 1,
|
||||||
|
topP: 0.95,
|
||||||
|
topK: 40,
|
||||||
|
maxOutputTokens: 8192,
|
||||||
|
responseMimeType: "text/plain",
|
||||||
|
};
|
||||||
|
|
||||||
|
async function run(message) {
|
||||||
|
const chatSession = model.startChat({
|
||||||
|
generationConfig,
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: "AI Guidelines:\n- The AI must only provide answers related to farming, including crops, sowing, irrigation, harvesting, fertilizers, pesticides, soil health, and climate conditions. \n- The AI must not answer anything outside farming. \n- If asked an unrelated question, the AI must not respond. \n- If the query is unclear, the AI must default to farming-related guidance. \n- The AI must provide only one-line responses without extra details. \n- No harmful, hurtful, or controversial responses. \n- No advice on illegal, unsafe, or unethical farming practices.\n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "model",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: "Understood. I will only provide one-line responses directly related to farming.\n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: "you can also receive json or javascript object as an input ",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "model",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: "Understood. I will process the JSON or JavaScript object only if it pertains to farming and respond with a single line.\n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: "Answer everything which falls under the farming and agriculture even if the prompt does not include any farm or agriculture word in it",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "model",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: "Understood. I will assume all queries are related to farming and provide a one-line farming-related response.\n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: "Give me a percise answer no matter what, dont give useless or incomplete answer \n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "model",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: "Understood. I will strive to provide precise and complete one-line farming-related answers.\n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await chatSession.sendMessage(message);
|
||||||
|
console.log(result.response.text());
|
||||||
|
return result.response.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { run };
|
||||||
|
|
||||||
|
// run("Sowing start and harvesting end which crop ?");
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const nodemailer = require("nodemailer");
|
||||||
|
|
||||||
|
const sendEmail = async (options) => {
|
||||||
|
const transporter = await nodemailer.createTransport({
|
||||||
|
service: "gmail",
|
||||||
|
host: process.env.HOST,
|
||||||
|
port: process.env.EMAIL_PORT,
|
||||||
|
secure: false,
|
||||||
|
auth: {
|
||||||
|
user: process.env.SMPT_MAIL, // senders email
|
||||||
|
pass: process.env.SMPT_PASSWORD, // app passoword created for your app by using step :: google account> manage your google account >security>enable two step verification > search app password and create password for your app
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mailOptions = {
|
||||||
|
from: "",
|
||||||
|
to: options.email,
|
||||||
|
subject: options.subject,
|
||||||
|
text: options.message,
|
||||||
|
};
|
||||||
|
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = sendEmail;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const cors = require("cors");
|
||||||
|
const cookieParser = require("cookie-parser");
|
||||||
|
|
||||||
|
const userRoute = require("./Routes/user.routes.js");
|
||||||
|
const farmRoute = require("./Routes/farm.routes.js");
|
||||||
|
const cropRoute = require("./Routes/crop.routes.js");
|
||||||
|
const financeRoute = require("./Routes/finance.routes.js");
|
||||||
|
const taskRoute = require("./Routes/task.routes.js");
|
||||||
|
const { checkAuthenticated } = require("./Middlewares/authentication.js");
|
||||||
|
const dotenv = require("dotenv");
|
||||||
|
const { run } = require("./Utils/model.js");
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: "./.env",
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const corsOptions = {
|
||||||
|
origin: process.env.FRONTEND_URI,
|
||||||
|
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||||
|
credentials: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use(cors(corsOptions));
|
||||||
|
app.use(express.json({ limit: "16kb" }));
|
||||||
|
app.use(express.urlencoded({ extended: true, limit: "16kb" }));
|
||||||
|
app.use(express.static("public"));
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
return res.send("Hiddskpkpk...");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use("/api/v1", userRoute);
|
||||||
|
|
||||||
|
app.use("/api/v1/farm", farmRoute);
|
||||||
|
|
||||||
|
app.use("/api/v1/crop", cropRoute);
|
||||||
|
|
||||||
|
app.use("/api/v1/finance", financeRoute);
|
||||||
|
|
||||||
|
app.use("/api/v1/task", taskRoute);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
const server = require("./app.js");
|
||||||
|
const dotenv = require("dotenv");
|
||||||
|
const cloudinary = require("cloudinary").v2;
|
||||||
|
const DB_connect = require("./Database/DB_connect.js");
|
||||||
|
const app = require("./app.js");
|
||||||
|
|
||||||
|
// dotenv Configuration
|
||||||
|
dotenv.config({
|
||||||
|
path: "./.env",
|
||||||
|
});
|
||||||
|
|
||||||
|
cloudinary.config({
|
||||||
|
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
||||||
|
api_key: process.env.CLOUDINARY_API_KEY,
|
||||||
|
api_secret: process.env.CLOUDINARY_API_SECRET,
|
||||||
|
});
|
||||||
|
|
||||||
|
DB_connect();
|
||||||
|
|
||||||
|
// Listening the port
|
||||||
|
app.listen(process.env.PORT, () => {
|
||||||
|
console.log("Server is Running on ", process.env.PORT);
|
||||||
|
console.log("Frontend URI : ", process.env.FRONTEND_URI);
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "nodemon index.js"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"@google/generative-ai": "^0.22.0",
|
||||||
|
"@huggingface/transformers": "^3.3.3",
|
||||||
|
"@xenova/transformers": "^2.17.2",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"cloudinary": "^2.5.1",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.19.2",
|
||||||
|
"jimp": "^1.6.0",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"mongoose": "^8.6.1",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"nodemailer": "^6.9.15",
|
||||||
|
"socket.io": "^4.7.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { pipeline } from "@xenova/transformers";
|
||||||
|
import Jimp from "jimp";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const modelName = "Xenova/distilbert-base-uncased-finetuned-sst-2-english"; // Example model
|
||||||
|
|
||||||
|
// Load the model
|
||||||
|
const classifier = await pipeline("image-classification", modelName);
|
||||||
|
|
||||||
|
// Load the image
|
||||||
|
let image;
|
||||||
|
try {
|
||||||
|
image = await Jimp.read("/home/karan/Downloads/tomato.png");
|
||||||
|
image.rgba(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error: Unable to open image. Check the file type or path.");
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert image to buffer
|
||||||
|
const buffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
|
|
||||||
|
// Classify the image
|
||||||
|
const result = await classifier(buffer);
|
||||||
|
|
||||||
|
console.log("Predicted class:", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# React + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import react from 'eslint-plugin-react'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,jsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
ecmaFeatures: { jsx: true },
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
settings: { react: { version: '18.3' } },
|
||||||
|
plugins: {
|
||||||
|
react,
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...js.configs.recommended.rules,
|
||||||
|
...react.configs.recommended.rules,
|
||||||
|
...react.configs['jsx-runtime'].rules,
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react/jsx-no-target-blank': 'off',
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/images/logo.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Crop Compass</title>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/flowbite@2.5.1/dist/flowbite.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/flowbite@2.5.1/dist/flowbite.min.js"></script>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^2.2.7",
|
||||||
|
"@splinetool/react-spline": "^4.0.0",
|
||||||
|
"apexcharts": "^4.5.0",
|
||||||
|
"chart.js": "^4.4.8",
|
||||||
|
"flowbite": "^2.5.1",
|
||||||
|
"framer-motion": "^12.4.7",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-apexcharts": "^1.7.0",
|
||||||
|
"react-chartjs-2": "^5.3.0",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-icons": "^5.3.0",
|
||||||
|
"react-intersection-observer": "^9.15.1",
|
||||||
|
"react-player": "^2.16.0",
|
||||||
|
"react-redux": "^9.1.2",
|
||||||
|
"react-router-dom": "^6.26.1",
|
||||||
|
"react-typewriter-effect": "^1.1.0",
|
||||||
|
"socket.io-client": "^4.7.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.9.0",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"eslint": "^9.9.0",
|
||||||
|
"eslint-plugin-react": "^7.35.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.9",
|
||||||
|
"globals": "^15.9.0",
|
||||||
|
"postcss": "^8.4.45",
|
||||||
|
"tailwindcss": "^3.4.10",
|
||||||
|
"vite": "^5.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 136 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="-2.4 -2.4 28.80 28.80" xmlns="http://www.w3.org/2000/svg" fill="#000000" stroke="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="3.2640000000000002"> <title></title> <g id="Complete"> <g id="add-square"> <g> <rect data-name="--Rectangle" fill="none" height="20" id="_--Rectangle" rx="2" ry="2" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" width="20" x="2" y="2"></rect> <line fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" x1="15.5" x2="8.5" y1="12" y2="12"></line> <line fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" x1="12" x2="12" y1="15.5" y2="8.5"></line> </g> </g> </g> </g><g id="SVGRepo_iconCarrier"> <title></title> <g id="Complete"> <g id="add-square"> <g> <rect data-name="--Rectangle" fill="none" height="20" id="_--Rectangle" rx="2" ry="2" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" width="20" x="2" y="2"></rect> <line fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" x1="15.5" x2="8.5" y1="12" y2="12"></line> <line fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" x1="12" x2="12" y1="15.5" y2="8.5"></line> </g> </g> </g> </g></svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 482 KiB |
|
After Width: | Height: | Size: 143 B |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 510 KiB |
|
After Width: | Height: | Size: 510 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
</defs>
|
||||||
|
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
|
||||||
|
<path d="M 37.712 41.541 c -2.437 -10.14 2.919 -19.609 8.772 -25.137 c -6.221 11.54 -7.41 20.104 -3.461 33.177 l 2.29 -0.854 c -0.882 -2.464 -1.413 -4.873 -1.685 -7.241 c 8.23 -2.355 13.883 -7.209 15.231 -15.926 C 59.796 13.651 52.042 6.72 43.718 0.117 c 3.04 9.758 -11.581 17.964 -10.296 30.949 c 0.271 2.741 0.697 5.33 1.326 7.825" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(127,178,65); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||||
|
<path d="M 34.069 30.999 c 0.917 -12.923 13.599 -21.098 9.649 -30.883 c 1.394 8.216 -9.771 12.38 -12.663 22.195 c -1.575 5.836 -1.151 11.452 3.693 16.579 C 34.119 36.396 33.937 33.751 34.069 30.999 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(113,156,64); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||||
|
<polygon points="68.77,61.09 70.46,47.61 43.69,47.61 19.54,47.61 21.23,61.09 " style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(160,126,99); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
||||||
|
<polyline points="63.11,61.09 59.5,90 44.01,90 30.5,90 26.89,61.09 " style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(160,126,99); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
||||||
|
<polygon points="62.61,65.09 63.11,61.09 26.89,61.09 27.39,65.09 " style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(145,107,77); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 307 KiB |
|
After Width: | Height: | Size: 779 KiB |
@@ -0,0 +1,74 @@
|
|||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import "./App.css";
|
||||||
|
//import Navbar from "./components/Navbar";
|
||||||
|
import Navbar2 from "./components/Navbar2";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { userSliceActions } from "./store/userSlice";
|
||||||
|
|
||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import { BACKEND_URL } from "./constants";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const user = useSelector((store) => store.user);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const loader = useSelector((store) => store.loader);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function initialiseUser() {
|
||||||
|
if (user.role == "unloggeduser") {
|
||||||
|
const responce = await fetch(`${BACKEND_URL}/api/v1/getuser`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
|
||||||
|
const userData = await responce.json();
|
||||||
|
|
||||||
|
//console.log("User Datae is ", userData);
|
||||||
|
|
||||||
|
dispatch(userSliceActions.addUser(userData.data));
|
||||||
|
|
||||||
|
//console.log("Updated User is : ", user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initialiseUser();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-full h-auto flex-col relative">
|
||||||
|
<div className="pt-20"></div>
|
||||||
|
<Outlet />
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
loader ? "block" : "hidden"
|
||||||
|
} absolute w-full h-full bg-black opacity-50 top-0 left-0`}
|
||||||
|
>
|
||||||
|
<div className="text-center my-96">
|
||||||
|
<div role="status">
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="inline w-12 h-12 text-gray-200 animate-spin dark:text-gray-600 fill-blue-900"
|
||||||
|
viewBox="0 0 100 101"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||||
|
fill="currentFill"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Container = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-full h-auto">{children}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Container;
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Td from "./Td";
|
||||||
|
import Loader from "./Loader";
|
||||||
|
|
||||||
|
const FarmList = () => {
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("http://localhost:8000/api/v1/farm", {
|
||||||
|
credentials: "include",
|
||||||
|
method: "GET",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => setData(data))
|
||||||
|
.then(setLoading(false))
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div className="relative overflow-x-auto shadow-md sm:rounded-lg">
|
||||||
|
{loading ? (
|
||||||
|
<Loader></Loader>
|
||||||
|
) : (
|
||||||
|
<table className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
||||||
|
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Farm name
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Location
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Type
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Size (acres)
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.length > 0 ? (
|
||||||
|
data.map((item) => <Td key={item.id} children={item} />)
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={5} className="text-center">
|
||||||
|
No data available
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FarmList;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
function FileInput() {
|
||||||
|
|
||||||
|
const handleFileChange= () => {
|
||||||
|
const fileInput = document.getElementById("file_input");
|
||||||
|
const file = fileInput.files[0];
|
||||||
|
if (!file) {
|
||||||
|
alert("Please select a file!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
|
// Send to backend
|
||||||
|
fetch("http://your-backend-url.com/upload", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => console.log("File uploaded successfully:", data))
|
||||||
|
.catch((error) => console.error("Upload failed:", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className=' pt-24'>
|
||||||
|
|
||||||
|
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" for="file_input">Upload file</label>
|
||||||
|
<input class="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
|
||||||
|
id="file_input"
|
||||||
|
type="file"
|
||||||
|
onChange={handleFileChange} />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileInput
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
const Loader = () => {
|
||||||
|
return (
|
||||||
|
<div className="w-full bg-white rounded-lg shadow p-4">
|
||||||
|
<div class="flex items-center justify-center w-full h-56 border border-gray-200 rounded-lg bg-gray-50 dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<div role="status">
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
|
||||||
|
viewBox="0 0 100 101"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||||
|
fill="currentFill"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loader;
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const Logs = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="relative overflow-y-hidden">
|
||||||
|
<table className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 border">
|
||||||
|
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Product name
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Color
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Category
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Price
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200">
|
||||||
|
<th
|
||||||
|
scope="row"
|
||||||
|
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white"
|
||||||
|
>
|
||||||
|
Apple MacBook Pro 17"
|
||||||
|
</th>
|
||||||
|
<td className="px-6 py-4">Silver</td>
|
||||||
|
<td className="px-6 py-4">Laptop</td>
|
||||||
|
<td className="px-6 py-4">$2999</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200">
|
||||||
|
<th
|
||||||
|
scope="row"
|
||||||
|
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white"
|
||||||
|
>
|
||||||
|
Microsoft Surface Pro
|
||||||
|
</th>
|
||||||
|
<td className="px-6 py-4">White</td>
|
||||||
|
<td className="px-6 py-4">Laptop PC</td>
|
||||||
|
<td className="px-6 py-4">$1999</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="bg-white dark:bg-gray-800">
|
||||||
|
<th
|
||||||
|
scope="row"
|
||||||
|
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white"
|
||||||
|
>
|
||||||
|
Magic Mouse 2
|
||||||
|
</th>
|
||||||
|
<td className="px-6 py-4">Black</td>
|
||||||
|
<td className="px-6 py-4">Accessories</td>
|
||||||
|
<td className="px-6 py-4">$99</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>{" "}
|
||||||
|
</table>{" "}
|
||||||
|
<Link to="/logs" className="text-[#2323FF] pl-5">
|
||||||
|
<hr />
|
||||||
|
View all Logs
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Logs;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Message = ({ message }) => {
|
||||||
|
const date = new Date();
|
||||||
|
return (
|
||||||
|
<div className="w-auto h-auto bg-gray-200 rounded-md text-start p-3 mx-4">
|
||||||
|
<p className="">{message}</p>
|
||||||
|
<p className="text-end text-sm ">
|
||||||
|
{date.getDate()}/{date.getMonth()}/{date.getFullYear()}{" "}
|
||||||
|
{date.toLocaleTimeString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Message;
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { BACKEND_URL } from "../constants";
|
||||||
|
|
||||||
|
const Navbar = () => {
|
||||||
|
const user = useSelector((store) => store.user);
|
||||||
|
|
||||||
|
//console.log("User is : ", user);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleLogOut = async () => {
|
||||||
|
const responce = await fetch(`${BACKEND_URL}/api/v1/logout`, {
|
||||||
|
method: "Get",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await responce.json();
|
||||||
|
|
||||||
|
//console.log("User Logged out data is : ", data);
|
||||||
|
|
||||||
|
if (data.success == true) {
|
||||||
|
navigate("/user/login");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<nav className="bg-[#3D8D7A] dark:bg-gray-900">
|
||||||
|
<div className="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
className="flex items-center space-x-3 rtl:space-x-reverse"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/logo.png"
|
||||||
|
className="size-12 mr-2 "
|
||||||
|
alt="Logo"
|
||||||
|
/>
|
||||||
|
<span className="self-center text-4xl font-bold whitespace-nowrap dark:text-white">
|
||||||
|
Crop Compass
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div className="flex items-center md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex text-sm bg-white rounded-full md:me-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600"
|
||||||
|
id="user-menu-button"
|
||||||
|
aria-expanded="false"
|
||||||
|
data-dropdown-toggle="user-dropdown"
|
||||||
|
data-dropdown-placement="bottom"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Open user menu</span>
|
||||||
|
<img
|
||||||
|
className="size-9 rounded-full"
|
||||||
|
src={`${user?.avatar}`}
|
||||||
|
alt="user photo"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{/* <!-- Dropdown menu --> */}
|
||||||
|
<div
|
||||||
|
className="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600 cursor-pointer"
|
||||||
|
id="user-dropdown"
|
||||||
|
>
|
||||||
|
{user?.name !== "Unloggedin User" && (
|
||||||
|
<div className="px-4 py-3">
|
||||||
|
<span className="block text-sm text-gray-900 dark:text-white">
|
||||||
|
{user?.name}
|
||||||
|
</span>
|
||||||
|
<span className="block text-sm text-gray-500 truncate dark:text-gray-400">
|
||||||
|
{user?.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ul className="py-2" aria-labelledby="user-menu-button">
|
||||||
|
{user?.name !== "Unloggedin User" && (
|
||||||
|
<>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
to={"/user/dashboard"}
|
||||||
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
Dashboard
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
to={"/user/dashboard/settings"}
|
||||||
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
Earnings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{user?.name === "Unloggedin User" ? (
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
to={"/user/login"}
|
||||||
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
onClick={handleLogOut}
|
||||||
|
className="block px-4 py-2 text-sm text-[#FBFFE4] hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
data-collapse-toggle="navbar-user"
|
||||||
|
type="button"
|
||||||
|
className="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
|
||||||
|
aria-controls="navbar-user"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Open main menu</span>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 17 14"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M1 1h15M1 7h15M1 13h15"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="items-center justify-between hidden w-full md:flex md:w-auto md:order-1"
|
||||||
|
id="navbar-user"
|
||||||
|
>
|
||||||
|
<ul className="flex text-xl flex-col font-medium p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-[#A3D1C6] md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-[#3D8D7A] dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
to={"/"}
|
||||||
|
className="block py-2 px-3 bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500"
|
||||||
|
aria-current="page"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
|
>
|
||||||
|
Services
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
|
>
|
||||||
|
Pricing
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
|
>
|
||||||
|
Contact
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const Navbar2 = () => {
|
||||||
|
|
||||||
|
const [isLoggedIn, setLoggedIn] = useState(false)
|
||||||
|
return (
|
||||||
|
<div className=" flex justify-center rounded-full">
|
||||||
|
<nav className=" h-18 mt-3 mb-3 w-[97.5%] mx-5 fixed z-20 dark:bg-gray-800/30 backdrop-blur-md rounded-full">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-gray-100/10 to-gray-500/20 pointer-events-none rounded-full"></div>
|
||||||
|
<div className="relative h-full flex items-center justify-between p-4">
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
className="flex items-center space-x-3 rtl:space-x-reverse"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/logo.png"
|
||||||
|
className="size-10"
|
||||||
|
alt="Logo"
|
||||||
|
/>
|
||||||
|
<span className="self-center text-2xl text-white font-semibold whitespace-nowrap dark:text-white">
|
||||||
|
Crop Compass
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div className="flex md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-black bg-white hover:bg-purple-200 font-medium rounded-full text-sm py-2 px-4 text-center"
|
||||||
|
>
|
||||||
|
{isLoggedIn? (
|
||||||
|
<Link to={"/user/dashboard/addfarm"}>Get Started</Link>
|
||||||
|
) : (
|
||||||
|
<Link to={"/user/login"}>Log In</Link>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="items-center justify-between hidden w-full md:flex md:w-auto md:order-1"
|
||||||
|
id="navbar-sticky"
|
||||||
|
>
|
||||||
|
<ul className="flex flex-col p-4 md:p-0 mt-4 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 bg-transparent font-bold">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block py-2 px-3 text-gray-50 rounded-sm md:bg-transparent md:text-gray-50 md:p-0 md:dark:text-gray-50"
|
||||||
|
aria-current="page"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block py-2 px-3 text-gray-50 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:hover:text-gray-50 md:p-0 md:dark:hover:text-gray-100 dark:text-white dark:hover:bg-purple-400 dark:hover:text-white md:dark:hover:bg-transparent "
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block py-2 px-3 text-gray-50 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:hover:text-gray-50 md:p-0 md:dark:hover:text-gray-100 dark:text-white dark:hover:bg-purple-400 dark:hover:text-white md:dark:hover:bg-transparent `0"
|
||||||
|
>
|
||||||
|
Services
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block py-2 px-3 text-gray-50 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:hover:text-gray-50 md:p-0 md:dark:hover:text-gray-50 dark:text-white dark:hover:bg-purple-400 dark:hover:text-white md:dark:hover:bg-transparent `0"
|
||||||
|
>
|
||||||
|
Contact
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar2;
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Notification = ({ notification }) => {
|
||||||
|
let timeStringToDayName = (dateStr) => {
|
||||||
|
// for getting day name by time string
|
||||||
|
// const dateStr = "2024-09-26T04:31:50.646+00:00";
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
const dayName = date.toLocaleDateString("en-US", { weekday: "long" });
|
||||||
|
//console.log(dayName);
|
||||||
|
return dayName;
|
||||||
|
};
|
||||||
|
|
||||||
|
let timeStringtoRealTime = (utcDateStr) => {
|
||||||
|
// for converting the to get time in am or pm
|
||||||
|
//const utcDateStr = "2024-09-26T04:31:50.646+00:00";
|
||||||
|
const date = new Date(utcDateStr);
|
||||||
|
// India TimeZone is Asia/Kolkata, which is UTC+5:30
|
||||||
|
const options = {
|
||||||
|
timeZone: "Asia/Kolkata",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
second: "numeric",
|
||||||
|
hour12: true,
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
};
|
||||||
|
|
||||||
|
const istDate = date.toLocaleString("en-US", options);
|
||||||
|
//console.log(istDate); // Output: "September 26, 2024, 10:01:50 AM"
|
||||||
|
return istDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { message, timestamp, isRead } = notification;
|
||||||
|
|
||||||
|
const realTimeString = timeStringtoRealTime(timestamp);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between p-4 bg-white shadow rounded-lg"
|
||||||
|
onClick={() => {
|
||||||
|
isRead = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<img
|
||||||
|
src="https://placehold.co/40x40"
|
||||||
|
alt="Profile picture of the user who followed you"
|
||||||
|
className="w-10 h-10 rounded-full mr-3"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-900">{message}</p>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
{timeStringToDayName(timestamp)},
|
||||||
|
{realTimeString.substring(21, 26) +
|
||||||
|
" " +
|
||||||
|
realTimeString.substring(30)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<p className="text-gray-500 mr-3">{realTimeString.substring(0, 18)}</p>
|
||||||
|
<span
|
||||||
|
className={`w-3 h-3 bg-blue-600 rounded-full ${isRead && "hidden"}`}
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import EditFarm from "../pages/UserPanel/Farm/EditFarm";
|
||||||
|
|
||||||
|
const Td = ({ children }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<tr className="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700 border-gray-200">
|
||||||
|
<td
|
||||||
|
className="px-6 py-4"
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`farmpage/${children._id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children.name}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4">{children.location}</td>
|
||||||
|
<td className="px-6 py-4">{children.soilType}</td>
|
||||||
|
<td className="px-6 py-4">{children.size}</td>
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
<EditFarm _id={children._id}></EditFarm>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Td;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
const TotalSpent = () => {
|
||||||
|
return (
|
||||||
|
<div className="h-full">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="h-full block max-w-sm p-6 bg-no-repeat bg-center bg-cover border border-gray-200 rounded-lg shadow-sm hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<h5 className="mb-2 text-4xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||||
|
₹10,000
|
||||||
|
</h5>
|
||||||
|
<p className="font-normal text-gray-700 dark:text-gray-400">
|
||||||
|
This is the total cost which you spent on this farm
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TotalSpent;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// ActivityFeed.jsx
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const ActivityFeed = () => {
|
||||||
|
// Hard-coded activity feed data
|
||||||
|
const activities = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
description: "Farm A reported increased yield",
|
||||||
|
time: "10 mins ago",
|
||||||
|
},
|
||||||
|
{ id: 2, description: "Sensor B recalibrated", time: "20 mins ago" },
|
||||||
|
{ id: 3, description: "Alert triggered on Farm C", time: "30 mins ago" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{activities.map((activity) => (
|
||||||
|
<li key={activity.id} className="bg-gray-50 p-2 rounded shadow">
|
||||||
|
<p className="text-sm">{activity.description}</p>
|
||||||
|
<span className="text-xs text-gray-500">{activity.time}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActivityFeed;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// AlertsPanel.jsx
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const AlertsPanel = () => {
|
||||||
|
// Hard-coded alerts data
|
||||||
|
const alerts = [
|
||||||
|
{ id: 1, message: "Temperature exceeds threshold", type: "warning" },
|
||||||
|
{ id: 2, message: "New sensor connected", type: "info" },
|
||||||
|
{ id: 3, message: "Power consumption high", type: "warning" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{alerts.map((alert) => (
|
||||||
|
<div
|
||||||
|
key={alert.id}
|
||||||
|
className={`p-2 rounded mb-2 ${
|
||||||
|
alert.type === "warning" ? "bg-red-100" : "bg-blue-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p className="text-sm">{alert.message}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AlertsPanel;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// MetricsCard.jsx
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const MetricsCard = ({ title, value }) => {
|
||||||
|
return (
|
||||||
|
<div className="bg-white p-4 rounded-lg shadow">
|
||||||
|
<h3 className="text-gray-500 text-sm font-medium">{title}</h3>
|
||||||
|
<p className="text-2xl font-bold">{value}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetricsCard;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// PerformanceChart.jsx
|
||||||
|
import React from "react";
|
||||||
|
import { Line } from "react-chartjs-2";
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
LineElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
} from "chart.js";
|
||||||
|
|
||||||
|
ChartJS.register(
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
LineElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend
|
||||||
|
);
|
||||||
|
|
||||||
|
const PerformanceChart = () => {
|
||||||
|
const data = {
|
||||||
|
labels: [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Dec",
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Yield",
|
||||||
|
data: [75, 68, 85, 88, 60, 62, 78, 90, 95, 92, 88, 80], // slightly improved values
|
||||||
|
fill: false,
|
||||||
|
backgroundColor: "rgb(75, 192, 192)",
|
||||||
|
borderColor: "rgba(75, 192, 192, 0.2)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { position: "top" },
|
||||||
|
title: { display: true, text: "Performance Trend" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Line data={data} options={options} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PerformanceChart;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import Chart from "react-apexcharts";
|
||||||
|
|
||||||
|
const Piechart = () => {
|
||||||
|
const [series, setSeries] = useState([35.1, 23.5, 2.4, 5.4]);
|
||||||
|
|
||||||
|
const chartOptions = {
|
||||||
|
series: series,
|
||||||
|
labels: ["Fertilizers", "Pestisides", "Manner", "Urea"],
|
||||||
|
colors: ["#1C64F2", "#16BDCA", "#FDBA8C", "#E74694"],
|
||||||
|
chart: {
|
||||||
|
type: "donut",
|
||||||
|
height: 320,
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
pie: {
|
||||||
|
donut: {
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
total: {
|
||||||
|
show: true,
|
||||||
|
label: "Total",
|
||||||
|
formatter: function (w) {
|
||||||
|
return w.globals.seriesTotals.reduce((a, b) => a + b, 0) + "k";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "bottom",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white md:p-6 block max-w-sm p-6 bg-no-repeat bg-center bg-cover border border-gray-200 rounded-lg shadow-sm hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
|
||||||
|
<h5 className="text-xl font-bold text-gray-900 dark:text-white mb-4 ">
|
||||||
|
Farm Info
|
||||||
|
</h5>
|
||||||
|
<Chart options={chartOptions} series={series} type="donut" height={320} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Piechart;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const BACKEND_URL = "http://localhost:8000";
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import React, { createContext, useMemo, useContext } from "react";
|
||||||
|
import { io } from "socket.io-client";
|
||||||
|
import { BACKEND_URL } from "../constants";
|
||||||
|
|
||||||
|
const SocketContext = createContext(null);
|
||||||
|
|
||||||
|
export const useSocket = () => {
|
||||||
|
const socket = useContext(SocketContext);
|
||||||
|
return socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SocketProvider = (props) => {
|
||||||
|
const socket = useMemo(
|
||||||
|
() =>
|
||||||
|
io(`${BACKEND_URL}`, {
|
||||||
|
withCredentials: true,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SocketContext.Provider value={socket}>
|
||||||
|
{props.children}
|
||||||
|
</SocketContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-midnight: #A3D1C6;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
|
import App from "./App.jsx";
|
||||||
|
import "./index.css";
|
||||||
|
import { SocketProvider } from "./context/SocketProvider.jsx";
|
||||||
|
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import MentifyStore from "./store/index.js";
|
||||||
|
|
||||||
|
import LoginPage from "./pages/Login/LoginPage.jsx";
|
||||||
|
import SignupPage from "./pages/Login/SignupPage.jsx";
|
||||||
|
import MainUserPanel from "./pages/UserPanel/MainUserPanel.jsx";
|
||||||
|
import ForgetPassword from "./pages/Password/ForgetPassword.jsx";
|
||||||
|
import ResetPassword from "./pages/Password/ResetPassword.jsx";
|
||||||
|
|
||||||
|
import HomePage from "./pages/Home/HomePage.jsx";
|
||||||
|
import Dashboard from "./pages/UserPanel/Dashboard.jsx";
|
||||||
|
import History from "./pages/UserPanel/History.jsx";
|
||||||
|
import Notifications from "./pages/UserPanel/Notifications.jsx";
|
||||||
|
import Settings from "./pages/UserPanel/Settings.jsx";
|
||||||
|
import ScheduleMeeting from "./pages/UserPanel/ScheduleMeeting.jsx";
|
||||||
|
import Support from "./pages/UserPanel/Support.jsx";
|
||||||
|
import FeedBackAndRatings from "./pages/UserPanel/FeedBackAndRatings.jsx";
|
||||||
|
import Monitoring from "./pages/UserPanel/Monitoring.jsx";
|
||||||
|
import AddFarm from "./pages/UserPanel/Farm/AddFarm.jsx";
|
||||||
|
import UpdateFarm from "./pages/UserPanel/Farm/UpdateForm.jsx";
|
||||||
|
import FarmPage from "./pages/UserPanel/Farm/FarmPage.jsx";
|
||||||
|
import Ai from "./pages/UserPanel/Ai.jsx";
|
||||||
|
createRoot(document.getElementById("root")).render(
|
||||||
|
<StrictMode>
|
||||||
|
<Provider store={MentifyStore}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/ai" element={<Ai />} />
|
||||||
|
{/* Routes for the main App */}
|
||||||
|
<Route path="/" element={<App />}>
|
||||||
|
<Route index element={<HomePage />} />
|
||||||
|
<Route path="home" element={<HomePage />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* User related routes */}
|
||||||
|
<Route path="user" element={<App />}>
|
||||||
|
<Route path="login" element={<LoginPage />} />
|
||||||
|
<Route path="signup" element={<SignupPage />} />
|
||||||
|
<Route path="forgetpassword" element={<ForgetPassword />} />
|
||||||
|
<Route
|
||||||
|
path="api/v1/password/reset/:token"
|
||||||
|
element={<ResetPassword />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route path="dashboard" element={<MainUserPanel />}>
|
||||||
|
<Route index element={<Dashboard />} />
|
||||||
|
<Route path="history" element={<History />} />
|
||||||
|
<Route path="notifications" element={<Notifications />} />
|
||||||
|
<Route path="settings" element={<Settings />} />
|
||||||
|
<Route path="scheduledmeetings" element={<ScheduleMeeting />} />
|
||||||
|
<Route path="support" element={<Support />} />
|
||||||
|
<Route path="feedback" element={<FeedBackAndRatings />} />
|
||||||
|
<Route path="monitoring" element={<Monitoring />} />
|
||||||
|
<Route path="addfarm" element={<AddFarm />} />
|
||||||
|
<Route path="updatefarm" element={<UpdateFarm />} />
|
||||||
|
<Route path="farmpage/:farmId" element={<FarmPage />} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
</Provider>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const About = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="bg-white py-12 w-full flex justify-center">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between w-10/12 h-auto">
|
||||||
|
<div className="w-full md:w-4/5 object-contain flex justify-center items-center">
|
||||||
|
<img src="/images/calender.png" className="w-full h-auto" alt="" />
|
||||||
|
</div>
|
||||||
|
<div className="container mx-auto flex flex-col justify-around h-full w-full md:py-10">
|
||||||
|
<div className="text-center md:text-start flex flex-col justify-around h-full">
|
||||||
|
<h2 className="text-xl font-bold mb-4 text-yellow-600">
|
||||||
|
CUSTOMIZE WITH YOUR SCHEDULE
|
||||||
|
</h2>
|
||||||
|
<h1 className="text-2xl md:text-4xl md:font-extrabold font-bold mb-4">
|
||||||
|
Personalized Professional Online Mentor on Your Schedule
|
||||||
|
</h1>
|
||||||
|
<p className="text-base mb-8">
|
||||||
|
Our scheduling system allows you to select based on free time.
|
||||||
|
Lorem ipsum demo text for template. Keep track of your students
|
||||||
|
class and mentoring schedules, and never miss your Session. The
|
||||||
|
best online class scheduling system with easy accessibility.
|
||||||
|
Lorem ipsum is a placeholder text commonly used to demonstrate
|
||||||
|
the visual form
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-4 justify-center md:justify-start">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="focus:outline-none text-white bg-purple-700 hover:bg-purple-800 focus:ring-4 focus:ring-purple-300 font-medium rounded-lg text-base px-5 py-2.5 mb-2 dark:bg-purple-600 dark:hover:bg-purple-700 dark:focus:ring-purple-900"
|
||||||
|
>
|
||||||
|
Get started
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default About;
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const HeroSecn = () => {
|
||||||
|
return (
|
||||||
|
<section className=" py-40 w-full flex justify-center text-gray-100">
|
||||||
|
<div className="flex flex-col-reverse md:flex-row justify-between w-10/12 h-auto">
|
||||||
|
<div className="container mx-auto flex flex-col justify-between h-full w-full">
|
||||||
|
<div className="text-center md:text-start flex flex-col justify-around h-full">
|
||||||
|
<h1 className="text-6xl md:text-6xl md:w-2/3 md:font-extrabold font-bold">
|
||||||
|
Anything and Everything you Need to know About
|
||||||
|
</h1>
|
||||||
|
<p className="text-2xl font-semibold mb-8 ">
|
||||||
|
Your crops and their Health!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-4/5 object-contain flex justify-center items-center">
|
||||||
|
<img
|
||||||
|
src="/images/plant.png"
|
||||||
|
className="w-full h-auto rounded-3xl shadow-xl"
|
||||||
|
alt="plant"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CardWithImage = () => {
|
||||||
|
return (
|
||||||
|
<div className="max-w-sm rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<a href="#">
|
||||||
|
<img
|
||||||
|
className="rounded-t-lg"
|
||||||
|
src="https://i.pinimg.com/736x/07/2b/5f/072b5f6a1630d919ceee1a8569683cf7.jpg"
|
||||||
|
alt="plant"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<div className="p-6 backdrop-blur-md rounded-b-lg">
|
||||||
|
<a href="#">
|
||||||
|
<h5 className="mb-2 text-2xl font-bold tracking-tight text-white dark:text-white">
|
||||||
|
High tech, high yields?
|
||||||
|
</h5>
|
||||||
|
</a>
|
||||||
|
<p className="mb-3 font-normal text-white dark:text-gray-400">
|
||||||
|
The Kenyan farmers deploying AI to increase productivity This article
|
||||||
|
is more than 4 months old. AI apps are increasingly popular among
|
||||||
|
small-scale farmers seeking to improve the quality and quantity of
|
||||||
|
their crop.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://www.theguardian.com/world/2024/sep/30/high-tech-high-yields-the-kenyan-farmers-deploying-ai-to-increase-productivity"
|
||||||
|
target="_blank"
|
||||||
|
className="inline-flex shadow-md backdrop-blur-md bg-gradient-to-tr from-gray-700/20 to-gray-50/20 items-center px-3 py-2 text-sm font-medium text-center text-white rounded-lg hover:backdrop-blur-xl "
|
||||||
|
>
|
||||||
|
Read more
|
||||||
|
<svg
|
||||||
|
className="rtl:rotate-180 w-3.5 h-3.5 ms-2"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M1 5h12m0 0L9 1m4 4L9 9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CardOnlyText = (props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href={props.href}
|
||||||
|
target="_blank"
|
||||||
|
className="block max-w-sm p-6 rounded-lg shadow-md backdrop-blur-md dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-50 dark:text-white">
|
||||||
|
{" "}
|
||||||
|
{props.headingText}{" "}
|
||||||
|
</h5>
|
||||||
|
<p className="font-normal text-gray-50 dark:text-gray-400">
|
||||||
|
{" "}
|
||||||
|
{props.bodyText}{" "}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CardWithButton = () => {
|
||||||
|
return (
|
||||||
|
<div className="max-w-sm p-6 backdrop-blur-md rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<a
|
||||||
|
href="https://www.reuters.com/sustainability/land-use-biodiversity/comment-how-empowering-smallholder-farmers-with-ai-tools-can-bolster-global-food-2025-01-10/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-50 dark:text-white">
|
||||||
|
Empowering smallholder farmers with AI tools can bolster global food
|
||||||
|
security
|
||||||
|
</h5>
|
||||||
|
</a>
|
||||||
|
<p className="mb-3 font-normal text-gray-50 dark:text-gray-400">
|
||||||
|
{" "}
|
||||||
|
AI-powered weather forecasts help rural Indian farmers make informed
|
||||||
|
planting decisions, reducing debt and boosting savings.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://www.reuters.com/sustainability/land-use-biodiversity/comment-how-empowering-smallholder-farmers-with-ai-tools-can-bolster-global-food-2025-01-10/"
|
||||||
|
target="_blank"
|
||||||
|
className="inline-flex shadow-md backdrop-blur-md bg-gradient-to-tr from-gray-700/20 to-gray-50/20 items-center px-3 py-2 text-sm font-medium text-center text-white rounded-lg hover:backdrop-blur-xl "
|
||||||
|
>
|
||||||
|
Read more
|
||||||
|
<svg
|
||||||
|
className="rtl:rotate-180 w-3.5 h-3.5 ms-2"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M1 5h12m0 0L9 1m4 4L9 9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CardWithOnlyImage = () => {
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-sm bg-white rounded-lg shadow-xl dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<a
|
||||||
|
href="https://theprint.in/economy/telangana-is-the-success-story-of-indian-agritech-ai-tools-soil-testing-e-commerce-more/1630359/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className=" rounded-lg"
|
||||||
|
src="https://i.pinimg.com/736x/2b/2a/0f/2b2a0f7003bd3e4201573c1189d600de.jpg"
|
||||||
|
alt="product image"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cards = [
|
||||||
|
HeroSecn,
|
||||||
|
CardWithImage,
|
||||||
|
CardOnlyText,
|
||||||
|
CardWithButton,
|
||||||
|
CardWithImage,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default cards;
|
||||||
|
|
||||||
|
export const CardLayout = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<HeroSecn />
|
||||||
|
<div className=" flex justify-center">
|
||||||
|
<div className=" flex justify-between py-8 w-5/6 ">
|
||||||
|
<cardWithImage />
|
||||||
|
<div className="flex flex-col gap-10 justify-between ">
|
||||||
|
<cardOnlyText />
|
||||||
|
<cardWithButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className=" flex flex-col justify-between">
|
||||||
|
<cardWithOnlyImage />
|
||||||
|
<cardOnlyText />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Customization = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section
|
||||||
|
className="bg-gray-100 py-12 w-full flex justify-center"
|
||||||
|
style={{ backgroundColor: "#f0f0f0" }}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col-reverse md:flex-row justify-between w-10/12 h-auto">
|
||||||
|
<div className="container mx-auto flex flex-col justify-around h-full w-full md:py-10">
|
||||||
|
<div className="text-center md:text-start flex flex-col justify-around h-full">
|
||||||
|
<h2 className="text-xl font-bold mb-4 text-yellow-600">
|
||||||
|
CUSTOMIZE WITH YOUR SCHEDULE
|
||||||
|
</h2>
|
||||||
|
<h1 className="text-2xl md:text-4xl md:font-extrabold font-bold mb-4">
|
||||||
|
Talented and Qualified Tutors to Serve You for Help
|
||||||
|
</h1>
|
||||||
|
<p className="text-base mb-8">
|
||||||
|
Our scheduling system allows you to select based on free time.
|
||||||
|
Lorem ipsum demo text for template. Keep track of your students
|
||||||
|
class and tutoring schedules, and never miss your lectures. The
|
||||||
|
best online class scheduling system with easy accessibility.
|
||||||
|
Lorem ipsum is a placeholder text commonly used to demonstrate
|
||||||
|
the visual form
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-4 justify-center md:justify-start">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="focus:outline-none text-white bg-purple-700 hover:bg-purple-800 focus:ring-4 focus:ring-purple-300 font-medium rounded-lg text-base px-5 py-2.5 mb-2 dark:bg-purple-600 dark:hover:bg-purple-700 dark:focus:ring-purple-900"
|
||||||
|
>
|
||||||
|
Get started
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-4/5 object-contain flex justify-center items-center">
|
||||||
|
<img
|
||||||
|
src="/images/interaction2.png"
|
||||||
|
className="w-full h-auto"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Customization;
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<footer className="text-gray-50">
|
||||||
|
<div className="mx-auto w-full max-w-screen-xl p-4 py-6 lg:py-8">
|
||||||
|
<div className="md:flex md:justify-between">
|
||||||
|
<div className="mb-6 md:mb-0">
|
||||||
|
<a href="/" className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src="/images/logo.png"
|
||||||
|
className="h-9 rounded-full"
|
||||||
|
alt="Logo"
|
||||||
|
/>
|
||||||
|
<span className="self-center text-xl font-bold whitespace-nowrap dark:text-white">
|
||||||
|
Crop Compass
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-8 sm:gap-6 sm:grid-cols-3">
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-6 text-sm font-semibold text-gray-50 uppercase dark:text-white">
|
||||||
|
Resources
|
||||||
|
</h2>
|
||||||
|
<ul className="text-gray-50 dark:text-gray-400 font-medium">
|
||||||
|
<li className="mb-4">
|
||||||
|
<a href="https://flowbite.com/" className="hover:underline">
|
||||||
|
Flowbite
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://tailwindcss.com/"
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
Tailwind CSS
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-6 text-sm font-semibold text-gray-50 uppercase dark:text-white">
|
||||||
|
Follow us
|
||||||
|
</h2>
|
||||||
|
<ul className="text-gray-50 dark:text-gray-400 font-medium">
|
||||||
|
<li className="mb-4">
|
||||||
|
<a
|
||||||
|
href="https://github.com/themesberg/flowbite"
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
Github
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/4eeurUVvTy"
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-6 text-sm font-semibold text-gray-50 uppercase dark:text-white">
|
||||||
|
Legal
|
||||||
|
</h2>
|
||||||
|
<ul className="text-gray-50 dark:text-gray-400 font-medium">
|
||||||
|
<li className="mb-4">
|
||||||
|
<a href="#" className="hover:underline">
|
||||||
|
Privacy Policy
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" className="hover:underline">
|
||||||
|
Terms & Conditions
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr className="my-6 border-gray-50 sm:mx-auto dark:border-gray-700 lg:my-8" />
|
||||||
|
<div className="sm:flex sm:items-center sm:justify-between">
|
||||||
|
<span className="text-sm text-gray-50 sm:text-center dark:text-gray-400">
|
||||||
|
© 2024{" "}
|
||||||
|
<a href="/" className="hover:underline">
|
||||||
|
Crop Compass™
|
||||||
|
</a>
|
||||||
|
. All Rights Reserved.
|
||||||
|
</span>
|
||||||
|
<div className="flex mt-4 sm:justify-center sm:mt-0">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-gray-50 hover:text-gray-900 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 8 19"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M6.135 3H8V0H6.135a4.147 4.147 0 0 0-4.142 4.142V6H0v3h2v9.938h3V9h2.021l.592-3H5V3.591A.6.6 0 0 1 5.592 3h.543Z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Facebook page</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-gray-50 hover:text-gray-900 dark:hover:text-white ms-5"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 21 16"
|
||||||
|
>
|
||||||
|
<path d="M16.942 1.556a16.3 16.3 0 0 0-4.126-1.3 12.04 12.04 0 0 0-.529 1.1 15.175 15.175 0 0 0-4.573 0 11.585 11.585 0 0 0-.535-1.1 16.274 16.274 0 0 0-4.129 1.3A17.392 17.392 0 0 0 .182 13.218a15.785 15.785 0 0 0 4.963 2.521c.41-.564.773-1.16 1.084-1.785a10.63 10.63 0 0 1-1.706-.83c.143-.106.283-.217.418-.33a11.664 11.664 0 0 0 10.118 0c.137.113.277.224.418.33-.544.328-1.116.606-1.71.832a12.52 12.52 0 0 0 1.084 1.785 16.46 16.46 0 0 0 5.064-2.595 17.286 17.286 0 0 0-2.973-11.59ZM6.678 10.813a1.941 1.941 0 0 1-1.8-2.045 1.93 1.93 0 0 1 1.8-2.047 1.919 1.919 0 0 1 1.8 2.047 1.93 1.93 0 0 1-1.8 2.045Zm6.644 0a1.94 1.94 0 0 1-1.8-2.045 1.93 1.93 0 0 1 1.8-2.047 1.918 1.918 0 0 1 1.8 2.047 1.93 1.93 0 0 1-1.8 2.045Z" />
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Discord community</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-gray-50 hover:text-gray-900 dark:hover:text-white ms-5"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 17"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M20 1.892a8.178 8.178 0 0 1-2.355.635 4.074 4.074 0 0 0 1.8-2.235 8.344 8.344 0 0 1-2.605.98A4.13 4.13 0 0 0 13.85 0a4.068 4.068 0 0 0-4.1 4.038 4 4 0 0 0 .105.919A11.705 11.705 0 0 1 1.4.734a4.006 4.006 0 0 0 1.268 5.392 4.165 4.165 0 0 1-1.859-.5v.05A4.057 4.057 0 0 0 4.1 9.635a4.19 4.19 0 0 1-1.856.07 4.108 4.108 0 0 0 3.831 2.807A8.36 8.36 0 0 1 0 14.184 11.732 11.732 0 0 0 6.291 16 11.502 11.502 0 0 0 17.964 4.5c0-.177 0-.35-.012-.523A8.143 8.143 0 0 0 20 1.892Z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Twitter page</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-gray-50 hover:text-gray-900 dark:hover:text-white ms-5"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M10 .333A9.911 9.911 0 0 0 6.866 19.65c.5.092.678-.215.678-.477 0-.237-.01-1.017-.014-1.845-2.757.6-3.338-1.169-3.338-1.169a2.627 2.627 0 0 0-1.1-1.451c-.9-.615.07-.6.07-.6a2.084 2.084 0 0 1 1.518 1.021 2.11 2.11 0 0 0 2.884.823c.044-.503.268-.973.63-1.325-2.2-.25-4.516-1.1-4.516-4.9A3.832 3.832 0 0 1 4.7 7.068a3.56 3.56 0 0 1 .095-2.623s.832-.266 2.726 1.016a9.409 9.409 0 0 1 4.962 0c1.89-1.282 2.717-1.016 2.717-1.016.366.83.402 1.768.1 2.623a3.827 3.827 0 0 1 1.02 2.659c0 3.807-2.319 4.644-4.525 4.889a2.366 2.366 0 0 1 .673 1.834c0 1.326-.012 2.394-.012 2.72 0 .263.18.572.681.475A9.911 9.911 0 0 0 10 .333Z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">GitHub account</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-gray-50 hover:text-gray-900 dark:hover:text-white ms-5"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M10 0a10 10 0 1 0 10 10A10.009 10.009 0 0 0 10 0Zm6.613 4.614a8.523 8.523 0 0 1 1.93 5.32 20.094 20.094 0 0 0-5.949-.274c-.059-.149-.122-.292-.184-.441a23.879 23.879 0 0 0-.566-1.239 11.41 11.41 0 0 0 4.769-3.366ZM8 1.707a8.821 8.821 0 0 1 2-.238 8.5 8.5 0 0 1 5.664 2.152 9.608 9.608 0 0 1-4.476 3.087A45.758 45.758 0 0 0 8 1.707ZM1.642 8.262a8.57 8.57 0 0 1 4.73-5.981A53.998 53.998 0 0 1 9.54 7.222a32.078 32.078 0 0 1-7.9 1.04h.002Zm2.01 7.46a8.51 8.51 0 0 1-2.2-5.707v-.262a31.64 31.64 0 0 0 8.777-1.219c.243.477.477.964.692 1.449-.114.032-.227.067-.336.1a13.569 13.569 0 0 0-6.942 5.636l.009.003ZM10 18.556a8.508 8.508 0 0 1-5.243-1.8 11.717 11.717 0 0 1 6.7-5.332.509.509 0 0 1 .055-.02 35.65 35.65 0 0 1 1.819 6.476 8.476 8.476 0 0 1-3.331.676Zm4.772-1.462A37.232 37.232 0 0 0 13.113 11a12.513 12.513 0 0 1 5.321.364 8.56 8.56 0 0 1-3.66 5.73h-.002Z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Dribbble account</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const Hero = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className=" py-12 w-full flex justify-center pt-28 text-gray-100">
|
||||||
|
<div className="flex flex-col-reverse md:flex-row justify-between w-10/12 h-auto">
|
||||||
|
<div className="container mx-auto flex flex-col justify-between h-full w-full">
|
||||||
|
<div className="text-center md:text-start flex flex-col justify-around h-full">
|
||||||
|
<h1 className="text-6xl md:text-6xl md:w-2/3 md:font-extrabold font-bold">
|
||||||
|
Anything and Everything you Need to know About
|
||||||
|
</h1>
|
||||||
|
<p className="text-2xl font-semibold mb-8 ">
|
||||||
|
Your crops and their Health!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-4/5 object-contain flex justify-center items-center">
|
||||||
|
<img
|
||||||
|
src="/images/plant.png"
|
||||||
|
className="w-full h-auto rounded-3xl shadow-xl"
|
||||||
|
alt="plant"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className=" flex justify-center">
|
||||||
|
<div className=" flex justify-between py-8 w-5/6 ">
|
||||||
|
<div className="max-w-sm rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<a href="#">
|
||||||
|
<img
|
||||||
|
className="rounded-t-lg"
|
||||||
|
src="/images/plant.png"
|
||||||
|
alt="plant"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<div className="p-8 backdrop-blur-md rounded-b-lg">
|
||||||
|
<a href="#">
|
||||||
|
<h5 className="mb-2 text-2xl font-bold tracking-tight text-white dark:text-white">
|
||||||
|
Noteworthy technology acquisitions 2021
|
||||||
|
</h5>
|
||||||
|
</a>
|
||||||
|
<p className="mb-3 font-normal text-white dark:text-gray-400">
|
||||||
|
Here are the biggest enterprise technology acquisitions of 2021
|
||||||
|
so far, in reverse chronological order.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline-flex shadow-md backdrop-blur-md bg-gradient-to-tr from-gray-700/20 to-gray-50/20 items-center px-3 py-2 text-sm font-medium text-center text-white rounded-lg hover:backdrop-blur-xl "
|
||||||
|
>
|
||||||
|
Read more
|
||||||
|
<svg
|
||||||
|
className="rtl:rotate-180 w-3.5 h-3.5 ms-2"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M1 5h12m0 0L9 1m4 4L9 9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-10 justify-between ">
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block max-w-sm p-6 rounded-lg shadow-md backdrop-blur-md dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-50 dark:text-white">
|
||||||
|
Noteworthy technology acquisitions 2021
|
||||||
|
</h5>
|
||||||
|
<p className="font-normal text-gray-50 dark:text-gray-400">
|
||||||
|
Here are the biggest enterprise technology acquisitions of
|
||||||
|
2021 so far, in reverse chronological order.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-sm p-6 backdrop-blur-md rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<a href="#">
|
||||||
|
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-50 dark:text-white">
|
||||||
|
Noteworthy technology acquisitions 2021
|
||||||
|
</h5>
|
||||||
|
</a>
|
||||||
|
<p className="mb-3 font-normal text-gray-50 dark:text-gray-400">
|
||||||
|
Here are the biggest enterprise technology acquisitions of 2021
|
||||||
|
so far, in reverse chronological order.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline-flex shadow-md backdrop-blur-md bg-gradient-to-tr from-gray-700/20 to-gray-50/20 items-center px-3 py-2 text-sm font-medium text-center text-white rounded-lg hover:backdrop-blur-xl "
|
||||||
|
>
|
||||||
|
Read more
|
||||||
|
<svg
|
||||||
|
className="rtl:rotate-180 w-3.5 h-3.5 ms-2"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M1 5h12m0 0L9 1m4 4L9 9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className=" flex flex-col justify-between">
|
||||||
|
<div className="w-full max-w-sm bg-white rounded-lg shadow-xl dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<a href="#">
|
||||||
|
<img
|
||||||
|
className=" rounded-lg"
|
||||||
|
src="/images/plant.png"
|
||||||
|
alt="product image"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block max-w-sm p-6 backdrop-blur-xl rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-50 dark:text-white">
|
||||||
|
Noteworthy technology acquisitions 2021
|
||||||
|
</h5>
|
||||||
|
<p className="font-normal text-gray-50 dark:text-gray-400">
|
||||||
|
Here are the biggest enterprise technology acquisitions of
|
||||||
|
2021 so far, in reverse chronological order.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Hero;
|
||||||
|
|
||||||
|
// {grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useInView } from "react-intersection-observer";
|
||||||
|
import { CardOnlyText, CardWithButton, CardWithImage, CardWithOnlyImage, HeroSecn, } from "./Cards";
|
||||||
|
|
||||||
|
const ScrollReveal = ({ children, direction = "left" }) => {
|
||||||
|
const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.2 });
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
left: { opacity: 0, x: -100 },
|
||||||
|
right: { opacity: 0, x: 100 },
|
||||||
|
up: { opacity: 0, y: 100 },
|
||||||
|
down: { opacity: 0, y: -100 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
ref={ref}
|
||||||
|
initial={variants[direction]}
|
||||||
|
animate={inView ? { opacity: 1, x: 0, y: 0 } : {}}
|
||||||
|
transition={{ duration: 1.2, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Hero2() {
|
||||||
|
const myRef = document.querySelector('.scrollable-div')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ScrollReveal direction='up'>
|
||||||
|
<HeroSecn />
|
||||||
|
</ScrollReveal>
|
||||||
|
<div className=" flex justify-center">
|
||||||
|
<div className=" flex justify-between py-8 w-5/6 ">
|
||||||
|
|
||||||
|
<ScrollReveal direction='up'>
|
||||||
|
<CardWithImage />
|
||||||
|
</ScrollReveal>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-10 justify-between ">
|
||||||
|
<ScrollReveal direction='up' > <CardOnlyText headingText = {"AI for agriculture: How Indian farmers are harvesting innovation"} bodyText={"Farmers participating in the programme saw a 21% increase in chili yields per acre, a 9% reduction in pesticide use, a 5% decrease in fertilizer usage, and an 8% improvement in unit prices due to quality enhancements."} href={"https://www.weforum.org/impact/ai-for-agriculture-in-india/ "} /> </ScrollReveal>
|
||||||
|
<ScrollReveal direction='up'> <CardWithButton /> </ScrollReveal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className=" flex flex-col justify-between">
|
||||||
|
<ScrollReveal direction='up'> <CardOnlyText headingText={"SugarChain: Blockchain technology meets Agriculture"} bodyText={"The use of blockchain technology can help farmers automate processes with high trust, addressing issues like middlemen involvement and ensuring accurate compensation for their products"} href={"https://arxiv.org/abs/2301.08405"} /> </ScrollReveal>
|
||||||
|
<ScrollReveal direction='up'> <CardWithOnlyImage /> </ScrollReveal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Hero2
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Navbar from "../../components/Navbar";
|
||||||
|
import Hero from "./Hero";
|
||||||
|
import Testimonial from "./Testimonial";
|
||||||
|
import Hero2 from "./Hero2";
|
||||||
|
import Footer from "./Footer";
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className=" bg-[url(/images/bgphoto.png)] bg-no-repeat bg-cover">
|
||||||
|
<Hero2 />
|
||||||
|
<Testimonial />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Reviews = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/images/Review1.jpeg",
|
||||||
|
alt: "Bonnie image",
|
||||||
|
name: "Bonnie",
|
||||||
|
occupation: "Student",
|
||||||
|
review:
|
||||||
|
"As a student of this online education website, I can confidently say that it has been an incredible experience. The platform is user-friendly and making it easy for me to learn at my own pace.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
const ReviewSection = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="bg-gray-100 py-12">
|
||||||
|
<div className="container mx-auto">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className=" text-2xl md:text-4xl font-bold mb-4">OUR TESTIMONIALS</h2>
|
||||||
|
<h1 className="text-3xl md:text-6xl font-bold mb-4">
|
||||||
|
What Our Students Say About Us
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="default-carousel"
|
||||||
|
className="relative w-full"
|
||||||
|
data-carousel="slide"
|
||||||
|
>
|
||||||
|
{/* <!-- Carousel wrapper --> */}
|
||||||
|
<div className="relative h-64 overflow-hidden rounded-lg md:h-96">
|
||||||
|
{/* <!-- Item 1 --> */}
|
||||||
|
{Reviews.map((reviewList) => (
|
||||||
|
<div
|
||||||
|
className="hidden duration-700 ease-in-out w-full"
|
||||||
|
data-carousel-item
|
||||||
|
>
|
||||||
|
<div className="flex w-full items-center justify-center h-full gap-8">
|
||||||
|
{/* Review no 1 */}
|
||||||
|
|
||||||
|
{reviewList.map((review) => (
|
||||||
|
<div className="w-full max-w-sm bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700 md:py-2">
|
||||||
|
<div className="flex flex-col items-center pb-10 p-2">
|
||||||
|
<img
|
||||||
|
className="w-20 h-20 md:w-24 md:h-24 mb-3 rounded-full shadow-lg"
|
||||||
|
src={`${review.src}`}
|
||||||
|
alt={`${review.alt}`}
|
||||||
|
/>
|
||||||
|
<h5 className="mb-1 text-xl font-medium text-gray-900 dark:text-white">
|
||||||
|
{review.name}
|
||||||
|
</h5>
|
||||||
|
<span className="text-base font-semibold text-gray-500 dark:text-gray-400">
|
||||||
|
{review.occupation}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<p className="md:hidden text-sm text-gray-600 text-center">
|
||||||
|
{review.review.substring(0, 35)}....
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="hidden md:block text-sm text-gray-600 text-center">
|
||||||
|
{review.review.substring(0,200)}...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* <!-- Slider indicators --> */}
|
||||||
|
<div className="absolute z-30 flex -translate-x-1/2 bottom-5 left-1/2 space-x-3 rtl:space-x-reverse">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
aria-current="true"
|
||||||
|
aria-label="Slide 1"
|
||||||
|
data-carousel-slide-to="0"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
aria-current="false"
|
||||||
|
aria-label="Slide 2"
|
||||||
|
data-carousel-slide-to="1"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
aria-current="false"
|
||||||
|
aria-label="Slide 3"
|
||||||
|
data-carousel-slide-to="2"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
aria-current="false"
|
||||||
|
aria-label="Slide 4"
|
||||||
|
data-carousel-slide-to="3"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
aria-current="false"
|
||||||
|
aria-label="Slide 5"
|
||||||
|
data-carousel-slide-to="4"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
{/* <!-- Slider controls --> */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute top-0 start-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
|
||||||
|
data-carousel-prev
|
||||||
|
>
|
||||||
|
<span className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/30 dark:bg-gray-800/30 group-hover:bg-white/50 dark:group-hover:bg-gray-800/60 group-focus:ring-4 group-focus:ring-white dark:group-focus:ring-gray-800/70 group-focus:outline-none">
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4 text-white dark:text-gray-800 rtl:rotate-180"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 6 10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M5 1 1 5l4 4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Previous</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute top-0 end-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
|
||||||
|
data-carousel-next
|
||||||
|
>
|
||||||
|
<span className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/30 dark:bg-gray-800/30 group-hover:bg-white/50 dark:group-hover:bg-gray-800/60 group-focus:ring-4 group-focus:ring-white dark:group-focus:ring-gray-800/70 group-focus:outline-none">
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4 text-white dark:text-gray-800 rtl:rotate-180"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 6 10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="m1 9 4-4-4-4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Next</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReviewSection;
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { MdEngineering } from "react-icons/md";
|
||||||
|
import { FaLaptopCode } from "react-icons/fa";
|
||||||
|
import { LiaLanguageSolid } from "react-icons/lia";
|
||||||
|
import { MdScience } from "react-icons/md";
|
||||||
|
import { MdHistoryEdu } from "react-icons/md";
|
||||||
|
import { MdPsychology } from "react-icons/md";
|
||||||
|
import { CgWebsite } from "react-icons/cg";
|
||||||
|
import { FaReplyAll } from "react-icons/fa";
|
||||||
|
|
||||||
|
const SubjectList = [
|
||||||
|
{
|
||||||
|
icon: "FaLaptopCode",
|
||||||
|
title: "Programming",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "MdEngineering",
|
||||||
|
title: "Engineering",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "LiaLanguageSolid",
|
||||||
|
title: "Languages",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "MdScience",
|
||||||
|
title: "Science",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "MdHistoryEdu",
|
||||||
|
title: "History",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "MdPsychology",
|
||||||
|
title: "Psychology",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "CgWebsite",
|
||||||
|
title: "Web Design",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "FaReplyAll",
|
||||||
|
title: "See all",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const SubjectSection = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="bg-white py-12 flex justify-center">
|
||||||
|
<div className="w-11/12">
|
||||||
|
<div className="container mx-auto">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-xl md:text-4xl font-bold mb-4">
|
||||||
|
OUR TUTOR SUBJECTS
|
||||||
|
</h2>
|
||||||
|
<h1 className="text-4xl md:text-6xl font-bold mb-4">
|
||||||
|
Find Online Tutor in Any Subject
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-3 md:gap-4 mt-10 md:grid-cols-3 lg:grid-cols-4">
|
||||||
|
<div
|
||||||
|
id="toast-success"
|
||||||
|
className="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800 "
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 md:w-9 md:h-9 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
|
||||||
|
<FaLaptopCode className="text-base sm:text-xl" />
|
||||||
|
<span className="sr-only">Check icon</span>
|
||||||
|
</div>
|
||||||
|
<div className="ms-3 text-sm sm:text-xl font-bold text-black font-sans">
|
||||||
|
Programming
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="toast-success"
|
||||||
|
className="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 md:w-9 md:h-9 text-blue-500 bg-blue-100 rounded-lg dark:bg-blue-800 dark:text-blue-200">
|
||||||
|
<MdEngineering className="text-base sm:text-xl" />
|
||||||
|
<span className="sr-only">Check icon</span>
|
||||||
|
</div>
|
||||||
|
<div className="ms-3 text-sm sm:text-xl font-bold text-black font-sans">
|
||||||
|
Engineering
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="toast-success"
|
||||||
|
className="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 md:w-9 md:h-9 text-pink-500 bg-pink-100 rounded-lg dark:bg-pink-800 dark:text-pink-200">
|
||||||
|
<LiaLanguageSolid className="text-base sm:text-xl" />
|
||||||
|
<span className="sr-only">Check icon</span>
|
||||||
|
</div>
|
||||||
|
<div className="ms-3 text-base sm:text-xl font-bold text-black font-sans">
|
||||||
|
Languages
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="toast-success"
|
||||||
|
className="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 md:w-9 md:h-9 text-purple-500 bg-purple-100 rounded-lg dark:bg-purple-800 dark:text-purple-200">
|
||||||
|
<MdScience className="text-base sm:text-xl" />
|
||||||
|
<span className="sr-only">Check icon</span>
|
||||||
|
</div>
|
||||||
|
<div className="ms-3 text-base sm:text-xl font-bold text-black font-sans">
|
||||||
|
Science
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="toast-success"
|
||||||
|
className="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 md:w-9 md:h-9 text-yellow-500 bg-yellow-100 rounded-lg dark:bg-yellow-800 dark:text-yellow-200">
|
||||||
|
<MdHistoryEdu className="text-base sm:text-xl" />
|
||||||
|
<span className="sr-only">Check icon</span>
|
||||||
|
</div>
|
||||||
|
<div className="ms-3 text-base sm:text-xl font-bold text-black font-sans">
|
||||||
|
History
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="toast-success"
|
||||||
|
className="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 md:w-9 md:h-9 text-red-500 bg-red-100 rounded-lg dark:bg-red-800 dark:text-red-200">
|
||||||
|
<MdPsychology className="text-base sm:text-xl" />
|
||||||
|
<span className="sr-only">Check icon</span>
|
||||||
|
</div>
|
||||||
|
<div className="ms-3 text-base sm:text-xl font-bold text-black font-sans">
|
||||||
|
Psychology
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="toast-success"
|
||||||
|
className="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800 md:hidden lg:flex"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 md:w-9 md:h-9 text-orange-500 bg-orange-100 rounded-lg dark:bg-orange-800 dark:text-orange-200 ">
|
||||||
|
<CgWebsite className="text-base sm:text-xl" />
|
||||||
|
<span className="sr-only">Check icon</span>
|
||||||
|
</div>
|
||||||
|
<div className="ms-3 text-base sm:text-xl font-bold text-black font-sans">
|
||||||
|
Web Design
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="toast-success"
|
||||||
|
className="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800 md:hidden lg:flex"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 md:w-9 md:h-9 text-gray-500 bg-gray-100 rounded-lg dark:bg-gray-800 dark:text-gray-200">
|
||||||
|
<FaReplyAll className="text-base sm:text-xl" />
|
||||||
|
<span className="sr-only">Check icon</span>
|
||||||
|
</div>
|
||||||
|
<div className="ms-3 text-base sm:text-xl font-bold text-black font-sans">
|
||||||
|
See all
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubjectSection;
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { IoMdContacts } from "react-icons/io";
|
||||||
|
import { FaClock } from "react-icons/fa";
|
||||||
|
import { FaMessage } from "react-icons/fa6";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useInView } from "react-intersection-observer";
|
||||||
|
import TypeWriterEffect from "react-typewriter-effect";
|
||||||
|
import cards from "./Cards";
|
||||||
|
|
||||||
|
const ScrollReveal = ({ children, direction = "left" }) => {
|
||||||
|
const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.5 });
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
left: { opacity: 0, x: -100 },
|
||||||
|
right: { opacity: 0, x: 100 },
|
||||||
|
up: { opacity: 0, y: 100 },
|
||||||
|
down: { opacity: 0, y: -100 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
ref={ref}
|
||||||
|
initial={variants[direction]}
|
||||||
|
animate={inView ? { opacity: 1, x: 0, y: 0 } : {}}
|
||||||
|
transition={{ duration: 1.2, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Testimonial = () => {
|
||||||
|
const myRef = document.querySelector(".scrollable-div");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className=" py-12 px-2 md:px-32 text-white">
|
||||||
|
<div className="container mx-auto ">
|
||||||
|
<div className="text-center flex-col justify-center align-middle ">
|
||||||
|
<ScrollReveal direction="up">
|
||||||
|
<h2 className="text-xl sm:text-4xl font-bold mb-4 drop-shadow-md">
|
||||||
|
WHY CHOOSE US?
|
||||||
|
</h2>
|
||||||
|
</ScrollReveal>
|
||||||
|
<ScrollReveal direction="up">
|
||||||
|
<h1 className="text-3xl text-center sm:text-6xl text-white drop-shadow-[10px_10px_10px_rgba(0,0,0,0.25)] font-bold mb-4">
|
||||||
|
<TypeWriterEffect
|
||||||
|
width="230"
|
||||||
|
trackWidth="13"
|
||||||
|
scrollArea={myRef}
|
||||||
|
startDelay={100}
|
||||||
|
cursorColor="white"
|
||||||
|
text="Record breaking features like never before!"
|
||||||
|
typeSpeed={100}
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
</ScrollReveal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row justify-around mt-8 h-auto">
|
||||||
|
<ScrollReveal direction="up">
|
||||||
|
<div className="max-w-sm p-6 backdrop-blur-md rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<svg
|
||||||
|
className="w-7 h-7 text-gray-200 dark:text-gray-400 mb-3"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path d="M18 5h-.7c.229-.467.349-.98.351-1.5a3.5 3.5 0 0 0-3.5-3.5c-1.717 0-3.215 1.2-4.331 2.481C8.4.842 6.949 0 5.5 0A3.5 3.5 0 0 0 2 3.5c.003.52.123 1.033.351 1.5H2a2 2 0 0 0-2 2v3a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V7a2 2 0 0 0-2-2ZM8.058 5H5.5a1.5 1.5 0 0 1 0-3c.9 0 2 .754 3.092 2.122-.219.337-.392.635-.534.878Zm6.1 0h-3.742c.933-1.368 2.371-3 3.739-3a1.5 1.5 0 0 1 0 3h.003ZM11 13H9v7h2v-7Zm-4 0H2v5a2 2 0 0 0 2 2h3v-7Zm6 0v7h3a2 2 0 0 0 2-2v-5h-5Z" />
|
||||||
|
</svg>
|
||||||
|
<a href="#">
|
||||||
|
<h5 className="mb-2 text-2xl font-semibold tracking-tight text-gray-50 dark:text-white">
|
||||||
|
Excellent Dashboards
|
||||||
|
</h5>
|
||||||
|
</a>
|
||||||
|
<p className="mb-3 font-normal text-gray-50 dark:text-gray-400">
|
||||||
|
Our descriptive dashboards give insights into your crop's
|
||||||
|
health and keeps track of your burning expenses
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline-flex font-medium items-center text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
Check Out
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3 ms-2.5 rtl:rotate-[270deg]"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 11v4.833A1.166 1.166 0 0 1 13.833 17H2.167A1.167 1.167 0 0 1 1 15.833V4.167A1.166 1.166 0 0 1 2.167 3h4.618m4.447-2H17v5.768M9.111 8.889l7.778-7.778"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</ScrollReveal>
|
||||||
|
|
||||||
|
<ScrollReveal direction="up">
|
||||||
|
<div className="max-w-sm p-6 backdrop-blur-md rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<svg
|
||||||
|
className="w-7 h-7 text-gray-200 dark:text-gray-400 mb-3"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path d="M18 5h-.7c.229-.467.349-.98.351-1.5a3.5 3.5 0 0 0-3.5-3.5c-1.717 0-3.215 1.2-4.331 2.481C8.4.842 6.949 0 5.5 0A3.5 3.5 0 0 0 2 3.5c.003.52.123 1.033.351 1.5H2a2 2 0 0 0-2 2v3a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V7a2 2 0 0 0-2-2ZM8.058 5H5.5a1.5 1.5 0 0 1 0-3c.9 0 2 .754 3.092 2.122-.219.337-.392.635-.534.878Zm6.1 0h-3.742c.933-1.368 2.371-3 3.739-3a1.5 1.5 0 0 1 0 3h.003ZM11 13H9v7h2v-7Zm-4 0H2v5a2 2 0 0 0 2 2h3v-7Zm6 0v7h3a2 2 0 0 0 2-2v-5h-5Z" />
|
||||||
|
</svg>
|
||||||
|
<a href="#">
|
||||||
|
<h5 className="mb-2 text-2xl font-semibold tracking-tight text-gray-50 dark:text-white">
|
||||||
|
{" "}
|
||||||
|
Crop Disease Prediction{" "}
|
||||||
|
</h5>
|
||||||
|
</a>
|
||||||
|
<p className="mb-3 font-normal text-gray-50 dark:text-gray-400">
|
||||||
|
Predict the possible crop diseases based on their shown
|
||||||
|
symptoms
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline-flex font-medium items-center text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
Check Out
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3 ms-2.5 rtl:rotate-[270deg]"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 11v4.833A1.166 1.166 0 0 1 13.833 17H2.167A1.167 1.167 0 0 1 1 15.833V4.167A1.166 1.166 0 0 1 2.167 3h4.618m4.447-2H17v5.768M9.111 8.889l7.778-7.778"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</ScrollReveal>
|
||||||
|
|
||||||
|
<ScrollReveal direction="up">
|
||||||
|
<div className="max-w-sm p-6 backdrop-blur-md rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<svg
|
||||||
|
className="w-7 h-7 text-gray-200 dark:text-gray-400 mb-3"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path d="M18 5h-.7c.229-.467.349-.98.351-1.5a3.5 3.5 0 0 0-3.5-3.5c-1.717 0-3.215 1.2-4.331 2.481C8.4.842 6.949 0 5.5 0A3.5 3.5 0 0 0 2 3.5c.003.52.123 1.033.351 1.5H2a2 2 0 0 0-2 2v3a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V7a2 2 0 0 0-2-2ZM8.058 5H5.5a1.5 1.5 0 0 1 0-3c.9 0 2 .754 3.092 2.122-.219.337-.392.635-.534.878Zm6.1 0h-3.742c.933-1.368 2.371-3 3.739-3a1.5 1.5 0 0 1 0 3h.003ZM11 13H9v7h2v-7Zm-4 0H2v5a2 2 0 0 0 2 2h3v-7Zm6 0v7h3a2 2 0 0 0 2-2v-5h-5Z" />
|
||||||
|
</svg>
|
||||||
|
<a href="#">
|
||||||
|
<h5 className="mb-2 text-2xl font-semibold tracking-tight text-gray-50 dark:text-white">
|
||||||
|
Crop Planner
|
||||||
|
</h5>
|
||||||
|
</a>
|
||||||
|
<p className="mb-3 font-normal text-gray-50 dark:text-gray-400">
|
||||||
|
Based on previous season's crop and used pertilizers and
|
||||||
|
pesticides, plan what crops would best suit the present state
|
||||||
|
of your soil
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline-flex font-medium items-center text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
Check Out
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3 ms-2.5 rtl:rotate-[270deg]"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 11v4.833A1.166 1.166 0 0 1 13.833 17H2.167A1.167 1.167 0 0 1 1 15.833V4.167A1.166 1.166 0 0 1 2.167 3h4.618m4.447-2H17v5.768M9.111 8.889l7.778-7.778"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</ScrollReveal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Testimonial;
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import React, { useRef } from "react";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { userSliceActions } from "../../store/userSlice";
|
||||||
|
import { BACKEND_URL } from "../../constants";
|
||||||
|
|
||||||
|
const LoginPage = () => {
|
||||||
|
const emailElement = useRef();
|
||||||
|
const passwordElement = useRef();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleLogin = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const responce = await fetch(`${BACKEND_URL}/api/v1/login`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: emailElement.current.value,
|
||||||
|
password: passwordElement.current.value,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await responce.json();
|
||||||
|
|
||||||
|
//console.log("User Login Data is here : ", user);
|
||||||
|
|
||||||
|
dispatch(userSliceActions.addUser(user.data));
|
||||||
|
|
||||||
|
emailElement.current.value = "";
|
||||||
|
passwordElement.current.value = "";
|
||||||
|
|
||||||
|
if (user.success == true) {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-[url(/images/loginBG.png)] bg-cover font-sans flex flex-col justify-center md:p-5 ">
|
||||||
|
<div className="container mx-auto ">
|
||||||
|
<div className="flex justify-between pt-24">
|
||||||
|
<div className=" rounded-lg shadow-md ">
|
||||||
|
<div className="flex flex-col items-center justify-center h-full ">
|
||||||
|
<h1 className="text-6xl font-bold text-white mb-4 md:text-6xl lg:text-9xl ml-8">
|
||||||
|
Welcome Back!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className=" backdrop-blur-md bg-gradient-to-tr from-slate-300/10 to-slate-200/30 rounded-lg shadow-md lg:p-36">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-50 mb-4">Login</h1>
|
||||||
|
<p className="text-gray-100 mb-6">
|
||||||
|
Welcome back! Please login to your account.
|
||||||
|
</p>
|
||||||
|
<form className="space-y-6" onSubmit={handleLogin}>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="username"
|
||||||
|
className="block mb-2 text-sm font-medium text-gray-100 dark:text-white"
|
||||||
|
>
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="username"
|
||||||
|
ref={emailElement}
|
||||||
|
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="username@gmail.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="password"
|
||||||
|
className="block mb-2 text-sm font-medium text-gray-100 dark:text-white"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
ref={passwordElement}
|
||||||
|
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="********"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
id="remember_me"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="remember_me"
|
||||||
|
className="ml-2 text-sm font-medium text-gray-100 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Remember Me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
to={"/user/forgetpassword"}
|
||||||
|
className="text-sm font-medium text-blue-600 hover:underline dark:text-blue-500"
|
||||||
|
>
|
||||||
|
Forgot Password?
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className=" flex justify-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="text-white w-1/2 backdrop-blur-lg bg-gradient-to-tr from-slate-100/15 to-slate-200/15 shadow-lg hover:backdrop-blur-lg focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-100 text-center mt-4">
|
||||||
|
New User?{" "}
|
||||||
|
<Link
|
||||||
|
to={"/user/signup"}
|
||||||
|
className="text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
Signup
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginPage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Navbar2 from "../../components/Navbar2.jsx";
|
||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import Container from "../../components/Container.jsx";
|
||||||
|
|
||||||
|
const MainLoginPage = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container>
|
||||||
|
<Outlet />
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainLoginPage;
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import React, { useRef } from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { BACKEND_URL } from "../../constants";
|
||||||
|
|
||||||
|
const SignupPage = () => {
|
||||||
|
const firstNameElement = useRef();
|
||||||
|
const lastNameElement = useRef();
|
||||||
|
const emailElement = useRef();
|
||||||
|
const roleElement = useRef();
|
||||||
|
const passwordElement = useRef();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleRegisteration = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
let userRole;
|
||||||
|
if (roleElement.current.value == "Student") {
|
||||||
|
userRole = "user";
|
||||||
|
} else {
|
||||||
|
userRole = "mentor";
|
||||||
|
}
|
||||||
|
const user = {
|
||||||
|
name:
|
||||||
|
firstNameElement.current.value + " " + lastNameElement.current.value,
|
||||||
|
email: emailElement.current.value,
|
||||||
|
password: passwordElement.current.value,
|
||||||
|
role: userRole,
|
||||||
|
};
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const responce = await fetch(`${BACKEND_URL}/api/v1/register`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(user),
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
const data = await responce.json();
|
||||||
|
|
||||||
|
//console.log("Our user data is : ", data);
|
||||||
|
|
||||||
|
firstNameElement.current.value = "";
|
||||||
|
lastNameElement.current.value = "";
|
||||||
|
emailElement.current.value = "";
|
||||||
|
passwordElement.current.value = "";
|
||||||
|
roleElement.current.value = "";
|
||||||
|
|
||||||
|
if (data.success == true) {
|
||||||
|
navigate("/user/login");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-[url(https://i.pinimg.com/736x/6c/d2/cc/6cd2cc05a7e464a78bdf1124b4ad30f1.jpg)] bg-cover font-sans flex flex-col justify-center md:p-5 ">
|
||||||
|
<div className="container mx-auto p-4 pt-24 sm:mt-32 md:mt-0">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div className="bg-gradient-to-br from-purple-300 to-pink-200 rounded-lg shadow-md min-h-40 object-center md:hidden">
|
||||||
|
<div className="flex flex-col items-center justify-center h-full relative">
|
||||||
|
<img
|
||||||
|
src="/images/background1.jpg"
|
||||||
|
alt=""
|
||||||
|
className="absolute order-3 w-full h-full"
|
||||||
|
/>
|
||||||
|
<h1 className="text-6xl font-bold text-white mb-4 md:text-6xl lg:text-9xl relative ml-8">
|
||||||
|
Welcome to Crop Compass
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="backdrop-blur-md bg-gradient-to-tr from-slate-300/10 to-slate-200/30 rounded-lg shadow-md lg:p-36">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-50 mb-4">
|
||||||
|
Register Your account
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-100">
|
||||||
|
Welcome to Crop Compass.
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-100 mb-6">
|
||||||
|
Please register your new account.
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
action="#"
|
||||||
|
className="space-y-6"
|
||||||
|
onSubmit={handleRegisteration}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-5 sm:flex-row">
|
||||||
|
<div className="w-full">
|
||||||
|
<label
|
||||||
|
htmlFor="username"
|
||||||
|
className="block mb-2 text-sm font-medium text-gray-100 dark:text-white"
|
||||||
|
>
|
||||||
|
First Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="firstName"
|
||||||
|
ref={firstNameElement}
|
||||||
|
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="John"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<label
|
||||||
|
htmlFor="username"
|
||||||
|
className="block mb-2 text-sm font-medium text-gray-100 dark:text-white"
|
||||||
|
>
|
||||||
|
Last Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="LastName"
|
||||||
|
ref={lastNameElement}
|
||||||
|
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="Doe"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="username"
|
||||||
|
className="block mb-2 text-sm font-medium text-gray-100 dark:text-white"
|
||||||
|
>
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
ref={emailElement}
|
||||||
|
className="bg-gray-50 border border-gray-300 text-gray-100 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="user@mail.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="password"
|
||||||
|
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
ref={passwordElement}
|
||||||
|
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="At least 6 unique Characters.. "
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
id="remember_me"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="remember_me"
|
||||||
|
className="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Remember Me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-sm font-medium text-blue-600 hover:underline dark:text-blue-500"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<div className=" flex justify-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="text-white w-1/2 backdrop-blur-lg bg-gradient-to-tr from-slate-100/15 to-slate-200/15 shadow-lg hover:backdrop-blur-lg focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||||
|
>
|
||||||
|
Register your Account
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 text-center mt-4">
|
||||||
|
Already have an Account ?{" "}
|
||||||
|
<Link
|
||||||
|
to={"/user/login"}
|
||||||
|
className="text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/* <div className=" backdrop-blur-sm rounded-lg shadow-md md:block">
|
||||||
|
<div className="flex flex-col items-center justify-center h-full relative text-center">
|
||||||
|
<img
|
||||||
|
src="/images/background1.jpg"
|
||||||
|
alt=""
|
||||||
|
className="absolute order-3 w-full h-full"
|
||||||
|
/>
|
||||||
|
<h1 className="text-5xl font-bold text-white mb-4 md:text-5xl lg:text-8xl relative ml-8 text-center">
|
||||||
|
Welcome to MentorFlux!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignupPage;
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import { IoIosKey } from "react-icons/io";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { FaArrowLeft } from "react-icons/fa6";
|
||||||
|
import { BACKEND_URL } from "../../constants";
|
||||||
|
|
||||||
|
const ForgetPassword = () => {
|
||||||
|
const emailElement = useRef();
|
||||||
|
|
||||||
|
const [status, setStatus] = useState(false);
|
||||||
|
|
||||||
|
const handleForgetPassword = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const responce = await fetch(`${BACKEND_URL}/api/v1//password/forgot`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: emailElement.current.value,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await responce.json();
|
||||||
|
|
||||||
|
if (data.success === true) {
|
||||||
|
setStatus(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="w-full h-[78vh] flex justify-center items-center">
|
||||||
|
<div className="">
|
||||||
|
<div className="flex flex-col items-center gap-12">
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<IoIosKey className="text-5xl bg-purple-200 p-2 rounded-full text-purple-500" />
|
||||||
|
<h2 className="text-3xl font-bold font-sans">Forget Password?</h2>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
No worries, we'll send you resent instructions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
className="flex flex-col w-full gap-5"
|
||||||
|
onSubmit={handleForgetPassword}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email"></label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
className="w-full rounded-md border-gray-400 border-2"
|
||||||
|
ref={emailElement}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="focus:outline-none text-white bg-purple-700 hover:bg-purple-800 focus:ring-4 focus:ring-purple-300 font-medium rounded-lg text-base px-5 py-2.5 mb-2 dark:bg-purple-600 dark:hover:bg-purple-700 dark:focus:ring-purple-900"
|
||||||
|
>
|
||||||
|
{status === false ? "Send Email" : "Email Sent to your Mail"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to={"/user/login"}
|
||||||
|
className="text-center text-gray-600 inline-flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
<FaArrowLeft className="text-lg" /> Back to Login Page
|
||||||
|
</Link>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ForgetPassword;
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import { RiLockPasswordFill } from "react-icons/ri";
|
||||||
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { FaArrowLeft } from "react-icons/fa6";
|
||||||
|
import { BACKEND_URL } from "../../constants";
|
||||||
|
|
||||||
|
const ResetPassword = () => {
|
||||||
|
const [secure, setSecure] = useState(true);
|
||||||
|
|
||||||
|
const newPassworElement = useRef();
|
||||||
|
const confirmPassworElement = useRef();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { token } = useParams();
|
||||||
|
|
||||||
|
// console.log("So our Token is : ", token);
|
||||||
|
|
||||||
|
const handleResetPassword = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (
|
||||||
|
confirmPassworElement.current.value !== newPassworElement.current.value
|
||||||
|
) {
|
||||||
|
setSecure(false);
|
||||||
|
} else {
|
||||||
|
const responce = await fetch(
|
||||||
|
`${BACKEND_URL}/api/v1/password/reset/${token}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
password: newPassworElement.current.value,
|
||||||
|
confirmPassword: confirmPassworElement.current.value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = await responce.json();
|
||||||
|
|
||||||
|
//console.log("Status of the Reset password", data);
|
||||||
|
|
||||||
|
if (data.success === true) {
|
||||||
|
navigate("/user/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-full h-[78vh] flex justify-center items-center">
|
||||||
|
<div className="">
|
||||||
|
<div className="flex flex-col items-center gap-10">
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<RiLockPasswordFill className="text-5xl bg-purple-200 p-2 rounded-full text-purple-500" />
|
||||||
|
<h2 className="text-3xl font-bold font-sans">
|
||||||
|
Create New Password
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
Create your new, unique and secure password here.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
className="flex flex-col w-full gap-5"
|
||||||
|
onSubmit={handleResetPassword}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label
|
||||||
|
htmlFor="passwordNew"
|
||||||
|
className="text-gray-500 font-semibold"
|
||||||
|
>
|
||||||
|
New Password :
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="passwordNew"
|
||||||
|
ref={newPassworElement}
|
||||||
|
placeholder="Enter your New Password"
|
||||||
|
className="w-full rounded-md border-gray-400 border-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label
|
||||||
|
htmlFor="passwordConfirm"
|
||||||
|
className="text-gray-500 font-semibold"
|
||||||
|
>
|
||||||
|
Confirm Password :
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="passwordConfirm"
|
||||||
|
ref={confirmPassworElement}
|
||||||
|
placeholder="Enter your Confirm Password"
|
||||||
|
className="w-full rounded-md border-gray-400 border-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className={`text-red-800 text-sm underline w-96 text-center ${
|
||||||
|
secure && "hidden"
|
||||||
|
} `}
|
||||||
|
>
|
||||||
|
Password and confirm Password is not same.Please Enter new
|
||||||
|
password and confirm Password same
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="focus:outline-none text-white bg-purple-700 hover:bg-purple-800 focus:ring-4 focus:ring-purple-300 font-medium rounded-lg text-base px-5 py-2.5 mb-2 dark:bg-purple-600 dark:hover:bg-purple-700 dark:focus:ring-purple-900"
|
||||||
|
>
|
||||||
|
Reset Password
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to={"/user/login"}
|
||||||
|
className="text-center text-gray-600 inline-flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
<FaArrowLeft className="text-lg" /> Back to Login Page
|
||||||
|
</Link>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ResetPassword;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
const Ai = () => {
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
|
const [prediction, setPrediction] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
if (e.target.files && e.target.files[0]) {
|
||||||
|
setSelectedFile(e.target.files[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!selectedFile) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setPrediction("");
|
||||||
|
setError("");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("image", selectedFile);
|
||||||
|
|
||||||
|
const response = await fetch("http://localhost:3000/predict", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Prediction request failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
// Assuming the API returns a JSON object with an "output" field.
|
||||||
|
setPrediction(data.output || "No prediction returned");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error during prediction:", err);
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-md mx-auto p-6 bg-white shadow rounded-lg">
|
||||||
|
<h2 className="text-2xl font-bold mb-4 text-center">
|
||||||
|
Plant disease prediction
|
||||||
|
</h2>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 p-2"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading || !selectedFile}
|
||||||
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
{loading ? "Predicting..." : "Predict"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{error && <p className="mt-4 text-center text-red-600">{error}</p>}
|
||||||
|
{prediction && (
|
||||||
|
<div className="mt-4 p-4 bg-green-100 border border-green-300 rounded">
|
||||||
|
<h3 className="text-lg font-semibold">Prediction:</h3>
|
||||||
|
<p>{prediction}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Ai;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import Piechart from "../../components/monitoring charts/Piechart";
|
||||||
|
import TotalSpent from "../../components/TotalSpent";
|
||||||
|
import FarmList from "../../components/FarmList";
|
||||||
|
import AddFarm from "./Farm/AddFarm";
|
||||||
|
|
||||||
|
const Dashboard = () => {
|
||||||
|
return (
|
||||||
|
<div className="w-full bg-white rounded-lg shadow p-4">
|
||||||
|
<div className="mb-4 flex justify-end space-x-4">
|
||||||
|
<AddFarm />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4 flex space-x-10">
|
||||||
|
<Piechart />
|
||||||
|
<TotalSpent />
|
||||||
|
</div>
|
||||||
|
<FarmList />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Dashboard;
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
const AddCrop = ({ farmId }) => {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [farm, setFarm] = useState(farmId);
|
||||||
|
const [harvestDate, setHarvestDate] = useState("");
|
||||||
|
const [growthStage, setGrowthStage] = useState("Planted");
|
||||||
|
|
||||||
|
const [healthStatus, setHealthStatus] = useState("");
|
||||||
|
const [image, setImage] = useState(null);
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [success, setSuccess] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setUploading(true);
|
||||||
|
setError("");
|
||||||
|
setSuccess("");
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("name", name);
|
||||||
|
formData.append("farm", farm);
|
||||||
|
formData.append("harvestDate", harvestDate);
|
||||||
|
formData.append("growthStage", growthStage);
|
||||||
|
formData.append("healthStatus", healthStatus);
|
||||||
|
if (image) {
|
||||||
|
formData.append("image", image);
|
||||||
|
}
|
||||||
|
console.log(formData);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://localhost:8000/api/v1/crop`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to create crop");
|
||||||
|
}
|
||||||
|
setSuccess("Crop created successfully!");
|
||||||
|
// Reset form fields
|
||||||
|
setName("");
|
||||||
|
setFarm("");
|
||||||
|
setHarvestDate("");
|
||||||
|
setGrowthStage("");
|
||||||
|
setHealthStatus("");
|
||||||
|
setImage(null);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setUploading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
className="block text-white bg-green-500 hover:bg-green-600 focus:ring-4 focus:outline-none focus:ring-green-300 rounded-lg text-sm px-5 py-2.5 text-center h-10 font-extrabold"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add New Crop
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto overflow-x-hidden">
|
||||||
|
{/* Backdrop */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
></div>
|
||||||
|
{/* Modal Content */}
|
||||||
|
<div className="relative w-full max-w-2xl mx-4">
|
||||||
|
<div className="relative bg-white rounded-xl shadow-lg">
|
||||||
|
{/* Decorative SVG Elements */}
|
||||||
|
<div className="absolute right-0 top-0 h-full w-48 overflow-hidden pointer-events-none">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 200 800"
|
||||||
|
className="absolute right-0 h-full transform translate-x-16 fill-green-100"
|
||||||
|
>
|
||||||
|
<path d="M0,0 Q100,200 0,400 Q100,600 0,800 L200,800 L200,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 200 800"
|
||||||
|
className="absolute right-0 h-full transform translate-x-8 fill-green-200/40"
|
||||||
|
>
|
||||||
|
<path d="M0,0 Q80,200 0,400 Q80,600 0,800 L200,800 L200,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
className="absolute right-12 top-20 w-16 h-16 fill-green-300/30"
|
||||||
|
>
|
||||||
|
<path d="M50,0 Q100,50 50,100 Q0,50 50,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
className="absolute right-16 top-48 w-12 h-12 fill-green-400/20"
|
||||||
|
>
|
||||||
|
<path d="M50,0 Q100,50 50,100 Q0,50 50,0 Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{/* Modal Header */}
|
||||||
|
<div className="flex items-center justify-between p-4 md:p-5 border-b rounded-t border-gray-200">
|
||||||
|
<h3 className="text-2xl font-semibold text-gray-800">
|
||||||
|
Add New Crop
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 inline-flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Close modal</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/* Modal Body */}
|
||||||
|
<div className="p-4 md:p-5">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="name"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Crop Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Rice"
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="harvestDate"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Harvest Date
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="harvestDate"
|
||||||
|
value={harvestDate}
|
||||||
|
onChange={(e) => setHarvestDate(e.target.value)}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="growthStage"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Growth Stage
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="growthStage"
|
||||||
|
value={growthStage}
|
||||||
|
onChange={(e) => setGrowthStage(e.target.value)}
|
||||||
|
className="mt-1 block w-full px-3 py-2 rounded-md border-gray-300 shadow-sm"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="Planted">Planted</option>
|
||||||
|
<option value="Growing">Growing</option>
|
||||||
|
<option value="Ready to Harvest">Ready to Harvest</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="healthStatus"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Health Status
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="healthStatus"
|
||||||
|
value={healthStatus}
|
||||||
|
onChange={(e) => setHealthStatus(e.target.value)}
|
||||||
|
placeholder="Healthy"
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="image"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Crop Image
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="image"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) => setImage(e.target.files[0])}
|
||||||
|
className="mt-1 block w-full"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 transition"
|
||||||
|
>
|
||||||
|
{uploading ? "Uploading..." : "Create Crop"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{error && <p className="text-red-600">{error}</p>}
|
||||||
|
{success && <p className="text-green-600">{success}</p>}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCrop;
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const AddFarm = () => {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [farmName, setFarmName] = useState("");
|
||||||
|
const [location, setLocation] = useState("");
|
||||||
|
const [waterContent, setWaterContent] = useState("");
|
||||||
|
const [sizeContent, setSizeContent] = useState("");
|
||||||
|
const [soilType, setSoilType] = useState("");
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
const navigator = useNavigate();
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const farmData = {
|
||||||
|
name: farmName,
|
||||||
|
location,
|
||||||
|
waterContent,
|
||||||
|
soilType,
|
||||||
|
size: sizeContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(farmData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("http://localhost:8000/api/v1/farm", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(farmData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to add farm");
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator("farmpage");
|
||||||
|
setSuccess(true);
|
||||||
|
setError(null);
|
||||||
|
setIsModalOpen(false);
|
||||||
|
window.location.reload();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
setSuccess(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setIsModalOpen(true);
|
||||||
|
}}
|
||||||
|
className="block text-white bg-green-600 hover:bg-green-700 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add New Farm
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 overflow-y-auto overflow-x-hidden flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div className="relative w-full max-w-2xl mx-4">
|
||||||
|
<div className="relative bg-white rounded-xl shadow-lg">
|
||||||
|
{/* Decorative Leaves */}
|
||||||
|
<div className="absolute right-0 top-0 h-full w-48 overflow-hidden pointer-events-none">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 200 800"
|
||||||
|
className="absolute right-0 h-full transform translate-x-16 fill-green-100"
|
||||||
|
>
|
||||||
|
<path d="M0,0 Q100,200 0,400 Q100,600 0,800 L200,800 L200,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 200 800"
|
||||||
|
className="absolute right-0 h-full transform translate-x-8 fill-green-200/40"
|
||||||
|
>
|
||||||
|
<path d="M0,0 Q80,200 0,400 Q80,600 0,800 L200,800 L200,0 Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
className="absolute right-12 top-20 w-16 h-16 fill-green-300/30"
|
||||||
|
>
|
||||||
|
<path d="M50,0 Q100,50 50,100 Q0,50 50,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
className="absolute right-16 top-48 w-12 h-12 fill-green-400/20"
|
||||||
|
>
|
||||||
|
<path d="M50,0 Q100,50 50,100 Q0,50 50,0 Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Header */}
|
||||||
|
<div className="flex items-center justify-between p-4 md:p-5 border-b rounded-t border-gray-200">
|
||||||
|
<h3 className="text-2xl font-semibold text-gray-800">
|
||||||
|
Add New Farm
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 inline-flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Close modal</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Body */}
|
||||||
|
<div className="p-4 md:p-5">
|
||||||
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="farmName"
|
||||||
|
className="block text-sm font-semibold text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Farm Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="farmName"
|
||||||
|
value={farmName}
|
||||||
|
onChange={(e) => setFarmName(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-green-400 focus:border-transparent transition duration-200 ease-in-out bg-white/80"
|
||||||
|
placeholder="Enter farm name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="location"
|
||||||
|
className="block text-sm font-semibold text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Location
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="location"
|
||||||
|
value={location}
|
||||||
|
onChange={(e) => setLocation(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-green-400 focus:border-transparent transition duration-200 ease-in-out bg-white/80"
|
||||||
|
placeholder="Enter location"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="waterContent"
|
||||||
|
className="block text-sm font-semibold text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Water Content
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="waterContent"
|
||||||
|
value={waterContent}
|
||||||
|
onChange={(e) => setWaterContent(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-green-400 focus:border-transparent transition duration-200 ease-in-out bg-white/80"
|
||||||
|
placeholder="Enter water content"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="waterContent"
|
||||||
|
className="block text-sm font-semibold text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Size of Land
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="waterContent"
|
||||||
|
value={sizeContent}
|
||||||
|
onChange={(e) => setSizeContent(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-green-400 focus:border-transparent transition duration-200 ease-in-out bg-white/80"
|
||||||
|
placeholder="Enter water content"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="soilType"
|
||||||
|
className="block text-sm font-semibold text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Soil Type
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="soilType"
|
||||||
|
value={soilType}
|
||||||
|
onChange={(e) => setSoilType(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-green-400 focus:border-transparent transition duration-200 ease-in-out bg-white/80"
|
||||||
|
placeholder="Enter soil type"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-6 rounded-lg transition duration-200 ease-in-out transform hover:scale-[1.02] focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
Add Farm
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p className="mt-4 text-red-500 text-sm bg-red-50 p-3 rounded-lg">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{success && (
|
||||||
|
<p className="mt-4 text-green-500 text-sm bg-green-50 p-3 rounded-lg">
|
||||||
|
Farm added successfully!
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddFarm;
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import Loader from "../../../components/Loader";
|
||||||
|
|
||||||
|
const AddTransaction = ({ farmId }) => {
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [type, setType] = useState("Expense");
|
||||||
|
const [amount, setAmount] = useState("");
|
||||||
|
const [description, setDescription] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setMessage("");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`http://localhost:8000/api/v1/finance/${farmId}/transaction`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type,
|
||||||
|
amount: parseFloat(amount),
|
||||||
|
description,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to create transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Transaction created:", data);
|
||||||
|
setMessage("Transaction created successfully!");
|
||||||
|
// Optionally clear the form
|
||||||
|
setType("Expense");
|
||||||
|
setAmount("");
|
||||||
|
setDescription("");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating transaction:", error);
|
||||||
|
setMessage("Error creating transaction.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setModalOpen(true)}
|
||||||
|
className="block text-white bg-green-600 hover:bg-green-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add Transaction
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{modalOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex justify-center items-center overflow-y-auto overflow-x-hidden bg-black bg-opacity-50">
|
||||||
|
<div className="relative p-4 w-full max-w-md">
|
||||||
|
<div className="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
|
{/* Modal Header */}
|
||||||
|
<div className="flex justify-between items-center p-4 border-b rounded-t dark:border-gray-600">
|
||||||
|
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
|
Add Transaction
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
onClick={() => setModalOpen(false)}
|
||||||
|
type="button"
|
||||||
|
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/* Modal Body */}
|
||||||
|
<div className="p-4">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="transaction-type"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Type
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="transaction-type"
|
||||||
|
value={type}
|
||||||
|
onChange={(e) => setType(e.target.value)}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="Expense">Expense</option>
|
||||||
|
<option value="Revenue">Revenue</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="transaction-amount"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Amount
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="transaction-amount"
|
||||||
|
value={amount}
|
||||||
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="transaction-description"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="transaction-description"
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
{loading ? <Loader></Loader> : "Add Transaction"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{message && (
|
||||||
|
<p className="mt-4 text-sm text-center text-green-600">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddTransaction;
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
const CreateTask = ({ farmId, onTaskCreated }) => {
|
||||||
|
const [farm, setFarm] = useState(farmId);
|
||||||
|
const [taskType, setTaskType] = useState("Sowing");
|
||||||
|
const [description, setDescription] = useState("Started Sowing the seeds");
|
||||||
|
const [assignedDate, setAssignedDate] = useState("2025-04-15");
|
||||||
|
const [status, setStatus] = useState("Pending");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setMessage("");
|
||||||
|
|
||||||
|
// In a real app, you might also fetch or choose a crop.
|
||||||
|
// For now, we assume crop is provided.
|
||||||
|
const crop = "67ba388f9c4979463e64a39a";
|
||||||
|
const taskData = {
|
||||||
|
farm,
|
||||||
|
crop,
|
||||||
|
taskType,
|
||||||
|
description,
|
||||||
|
assignedDate,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("http://localhost:8000/api/v1/task", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(taskData),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to create task");
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Task created:", data);
|
||||||
|
setMessage("Task created successfully!");
|
||||||
|
setModalOpen(false);
|
||||||
|
// Call the parent's callback with the newly created task
|
||||||
|
if (onTaskCreated) onTaskCreated(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating task:", error);
|
||||||
|
setMessage("Error creating task.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setModalOpen(true)}
|
||||||
|
className="block text-white bg-green-600 hover:bg-green-700 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Create Task
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{modalOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75">
|
||||||
|
<div className="relative w-full max-w-2xl p-6 bg-white rounded-lg shadow-xl">
|
||||||
|
<div className="flex justify-between items-center border-b pb-3">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800">Create Task</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => setModalOpen(false)}
|
||||||
|
type="button"
|
||||||
|
className="text-gray-600 hover:text-gray-800"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="task-type"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Task Type
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="task-type"
|
||||||
|
value={taskType}
|
||||||
|
onChange={(e) => setTaskType(e.target.value)}
|
||||||
|
className="mt-2 block w-full rounded-md border-gray-300 shadow-sm p-3 focus:ring-2 focus:ring-green-400"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="Sowing">Sowing</option>
|
||||||
|
<option value="Watering">Watering</option>
|
||||||
|
<option value="Fertilization">Fertilization</option>
|
||||||
|
<option value="Pest Control">Pest Control</option>
|
||||||
|
<option value="Harvesting">Harvesting</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="description"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 focus:ring-2 focus:ring-green-400"
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="assignedDate"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Assigned Date
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="assignedDate"
|
||||||
|
value={assignedDate}
|
||||||
|
onChange={(e) => setAssignedDate(e.target.value)}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 focus:ring-2 focus:ring-green-400"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="status"
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Status
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="status"
|
||||||
|
value={status}
|
||||||
|
onChange={(e) => setStatus(e.target.value)}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 focus:ring-2 focus:ring-green-400"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="Pending">Pending</option>
|
||||||
|
<option value="Completed">Completed</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full inline-flex justify-center py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
{loading ? "Creating Task..." : "Create Task"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{message && (
|
||||||
|
<p className="mt-4 text-center text-sm text-green-600">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateTask;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import Loader from "../../../components/Loader";
|
||||||
|
|
||||||
|
const CreateFinance = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
// Hardcoded farm ID from your example
|
||||||
|
const farmId = "67b9e6829c4979463e64a0fc";
|
||||||
|
|
||||||
|
const handleCreateFinance = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setMessage("");
|
||||||
|
try {
|
||||||
|
const response = await fetch("http://localhost:8000/api/v1/finance", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ farm: farmId }),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to create finance");
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Finance response:", data);
|
||||||
|
setMessage("Finance created successfully!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating finance:", error);
|
||||||
|
setMessage("Error creating finance.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleCreateFinance}
|
||||||
|
disabled={loading}
|
||||||
|
className="mt-4 w-30 inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
{loading ? <Loader></Loader> : "Create Finance"}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateFinance;
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import Loader from "../../../components/Loader";
|
||||||
|
|
||||||
|
const CropTable = ({ farmId }) => {
|
||||||
|
const [crops, setCrops] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const handleRemoveCrop = async (cropId) => {
|
||||||
|
try {
|
||||||
|
await fetch(`http://localhost:8000/api/v1/crop/${cropId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setCrops(crops.filter((crop) => crop._id !== cropId));
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCrops = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`http://localhost:8000/api/v1/crop/farm/${farmId}`,
|
||||||
|
{
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch crops");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setCrops(data || []);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCrops();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loader></Loader>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mt-4">
|
||||||
|
<p className="text-red-600">Error: {error}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crops.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-8 bg-gray-50 rounded-lg">
|
||||||
|
<h2 className="text-xl font-semibold text-gray-600">No crops found</h2>
|
||||||
|
<p className="text-gray-500 mt-2">
|
||||||
|
Start by adding some crops to your farm
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overflow-hidden rounded-lg border border-gray-200 bg-white shadow">
|
||||||
|
<div className="px-4 py-5 sm:px-6">
|
||||||
|
<h1 className="text-2xl font-semibold text-gray-900">Crops Table</h1>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-6 py-3 text-left text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Image
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-3 text-left text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Name
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-3 text-left text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Growth Stage
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-3 text-left text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Health Status
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-3 text-left text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Harvest Date
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-3 text-left text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Remove Crop
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
|
{crops.map((crop) => (
|
||||||
|
<tr key={crop._id} className="hover:bg-gray-50">
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="h-12 w-12 rounded-full overflow-hidden">
|
||||||
|
{crop.image ? (
|
||||||
|
<img
|
||||||
|
src={crop.image}
|
||||||
|
alt={crop.name}
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="h-full w-full bg-gray-200 flex items-center justify-center">
|
||||||
|
<span className="text-gray-500 text-xs">No image</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm font-medium text-gray-900">
|
||||||
|
{crop.name}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span
|
||||||
|
className="inline-flex px-2 py-1 text-sm font-medium rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
crop.growthStage === "Planted"
|
||||||
|
? "rgb(220, 252, 231)"
|
||||||
|
: crop.growthStage === "Growing"
|
||||||
|
? "rgb(254, 249, 195)"
|
||||||
|
: "rgb(224, 242, 254)",
|
||||||
|
color:
|
||||||
|
crop.growthStage === "Planted"
|
||||||
|
? "rgb(22, 101, 52)"
|
||||||
|
: crop.growthStage === "Growing"
|
||||||
|
? "rgb(113, 63, 18)"
|
||||||
|
: "rgb(3, 105, 161)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{crop.growthStage}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{crop.healthStatus}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{new Date(crop.harvestDate).toLocaleDateString()}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"
|
||||||
|
onClick={() => handleRemoveCrop(crop._id)}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CropTable;
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
const AddCrop = ({ farmId }) => {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [farm, setFarm] = useState(farmId);
|
||||||
|
const [harvestDate, setHarvestDate] = useState("");
|
||||||
|
const [growthStage, setGrowthStage] = useState("Planted");
|
||||||
|
const [healthStatus, setHealthStatus] = useState("");
|
||||||
|
const [image, setImage] = useState(null);
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [success, setSuccess] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setUploading(true);
|
||||||
|
setError("");
|
||||||
|
setSuccess("");
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("name", name);
|
||||||
|
formData.append("farm", farm);
|
||||||
|
formData.append("harvestDate", harvestDate);
|
||||||
|
formData.append("growthStage", growthStage);
|
||||||
|
formData.append("healthStatus", healthStatus);
|
||||||
|
if (image) {
|
||||||
|
formData.append("image", image);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://localhost:8000/api/v1/crop`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to create crop");
|
||||||
|
}
|
||||||
|
setSuccess("Crop created successfully!");
|
||||||
|
setName("");
|
||||||
|
setFarm("");
|
||||||
|
setHarvestDate("");
|
||||||
|
setGrowthStage("");
|
||||||
|
setHealthStatus("");
|
||||||
|
setImage(null);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setUploading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
className="block text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add New Crop
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto overflow-x-hidden">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
></div>
|
||||||
|
<div className="relative w-full max-w-3xl mx-4">
|
||||||
|
<div className="relative bg-white rounded-xl shadow-lg">
|
||||||
|
{/* Decorative SVG Elements - Moved further right */}
|
||||||
|
<div className="z-0 absolute right-0 top-0 h-full w-32 overflow-hidden pointer-events-none">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 200 800"
|
||||||
|
className="absolute right-0 h-full transform translate-x-16 fill-green-100"
|
||||||
|
>
|
||||||
|
<path d="M0,0 Q100,200 0,400 Q100,600 0,800 L200,800 L200,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 200 800"
|
||||||
|
className="absolute right-0 h-full transform translate-x-8 fill-green-200/40"
|
||||||
|
>
|
||||||
|
<path d="M0,0 Q80,200 0,400 Q80,600 0,800 L200,800 L200,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
className="absolute right-12 top-20 w-16 h-16 fill-green-300/30"
|
||||||
|
>
|
||||||
|
<path d="M50,0 Q100,50 50,100 Q0,50 50,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
className="absolute right-16 top-48 w-12 h-12 fill-green-400/20"
|
||||||
|
>
|
||||||
|
<path d="M50,0 Q100,50 50,100 Q0,50 50,0 Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Header */}
|
||||||
|
<div className="flex items-center justify-between p-6 border-b rounded-t border-gray-200">
|
||||||
|
<h3 className="text-2xl font-semibold text-gray-800">
|
||||||
|
Add New Crop
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm z-100 w-8 h-8 inline-flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Close modal</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Body */}
|
||||||
|
<div className="p-6">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6 max-w-2xl">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="name"
|
||||||
|
className="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Crop Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Rice"
|
||||||
|
className="block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="harvestDate"
|
||||||
|
className="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Harvest Date
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="harvestDate"
|
||||||
|
value={harvestDate}
|
||||||
|
onChange={(e) => setHarvestDate(e.target.value)}
|
||||||
|
className="block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="growthStage"
|
||||||
|
className="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Growth Stage
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="growthStage"
|
||||||
|
value={growthStage}
|
||||||
|
onChange={(e) => setGrowthStage(e.target.value)}
|
||||||
|
className="block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="Planted">Planted</option>
|
||||||
|
<option value="Growing">Growing</option>
|
||||||
|
<option value="Ready to Harvest">Ready to Harvest</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="healthStatus"
|
||||||
|
className="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Health Status
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="healthStatus"
|
||||||
|
value={healthStatus}
|
||||||
|
onChange={(e) => setHealthStatus(e.target.value)}
|
||||||
|
placeholder="Healthy"
|
||||||
|
className="block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="image"
|
||||||
|
className="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Crop Image
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="image"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) => setImage(e.target.files[0])}
|
||||||
|
className="block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-4">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full z-100 bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition duration-200 font-medium"
|
||||||
|
>
|
||||||
|
{uploading ? "Uploading..." : "Create Crop"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <p className="text-red-600 mt-4">{error}</p>}
|
||||||
|
{success && <p className="text-green-600 mt-4">{success}</p>}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCrop;
|
||||||