Initial import
2
Tripzy-main/backend/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
.env
|
||||
44
Tripzy-main/backend/app.js
Normal file
@ -0,0 +1,44 @@
|
||||
const express = require("express");
|
||||
const morgan = require("morgan");
|
||||
const cors = require("cors");
|
||||
|
||||
const errorController = require("./controllers/error-controller");
|
||||
const tripRouter = require("./routes/trip-route");
|
||||
const userRouter = require("./routes/user-route");
|
||||
const coinsRouter = require("./routes/coins-route");
|
||||
const enrolledTripsRouter = require("./routes/enrolled-trips-route");
|
||||
const activityRouter = require("./routes/activity-route");
|
||||
const itineraryRouter = require("./routes/itinerary-route");
|
||||
const suggessionRouter = require("./routes/suggession-route");
|
||||
|
||||
const AppError = require("./utils/app-error");
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.options("*", cors());
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.use(express.json({ limit: "10kb" }));
|
||||
app.use(express.urlencoded({ extended: true, limit: "10kb" }));
|
||||
|
||||
if (process.env.NODE_ENV === "development") app.use(morgan("dev"));
|
||||
|
||||
// routes
|
||||
app.use("/api/auth", userRouter);
|
||||
app.use("/api/coins", coinsRouter);
|
||||
app.use("/api/trips", tripRouter);
|
||||
app.use("/api/activities", activityRouter);
|
||||
app.use("/api/enrolledTrips", enrolledTripsRouter);
|
||||
app.use("/api/itineraries", itineraryRouter);
|
||||
app.use("/api/suggestions", suggessionRouter);
|
||||
|
||||
app.all("*", (req, res, next) => {
|
||||
next(new AppError(`Can't find ${req.originalUrl} on this server`, 404));
|
||||
});
|
||||
|
||||
// global error handler
|
||||
app.use(errorController);
|
||||
|
||||
module.exports = app;
|
||||
9
Tripzy-main/backend/config/firebase-config.js
Normal file
@ -0,0 +1,9 @@
|
||||
var admin = require("firebase-admin");
|
||||
|
||||
var serviceAccount = require("./serviceAccount.json");
|
||||
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert(serviceAccount),
|
||||
});
|
||||
|
||||
module.exports = admin;
|
||||
12
Tripzy-main/backend/config/serviceAccount.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "tripzy-code-crafters",
|
||||
"private_key_id": "f67c4b5caa381c0a1d1c0d8f120e31af10824a4e",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDQ39gy4DuEj9Nz\nxMJu0lmu65MuayEz5HntB2AszLiUJu9bhdGeRuBisaoBACNZiiumWF69bC5NwIlQ\n4XXNaCk4D8MRl2QxDSaZDfukDoWY9ptK9IFmIRUT1We1mqlM9PTm4FIn+A0vgkHn\nG+UK0QFMO6Kuh8QTz3Y9UU6g3jFWQ8vCz06K/gjEdB41RjwCTXUU7GZoQoKPyOEO\nB1tSmHY6oMzeFJbOpEZz51eB/2hnrIhISUWQd60vSank6MuieiqvZgOc5TE5+Tdv\nMwx2Rm8mF9J3nNcVsbdRAUZtNvG/Hf6uMM300V+eZDgb7Tomh6sOsH7wIx1Q1vL5\ntKLdab2PAgMBAAECgf92lqBaQkPXML2ry54iTV8ANecVadErJTRjBjlPhOvkbIVl\neu0vqtLiIje7z9hCG4kPQTCwR3Z4wjBH2PT/UXyzErYGZFoRziCvndRybhsJUUjG\nO9CeSQK4XY7JIF7rITwRsYWRGBMB8BAEf2WU3BU5KRjHw3StLSZ0gnA/byKAG6gX\nVZ2J6wk1YmZWYpz3ajTe/zdMxD+SL1G2hOVI2F6sWNDTHbqVIqCiKf6CWNJnxNE6\nZMZzCEyeih+YgVq4GyP2N3tMfrR5T8mFS76+meIc+1NSolEkXCp8ZDosFzbVg1kp\nt3nZxrMtw13oP7WLM1AWQ/PyWtYFYZZmnQsU22UCgYEA8zR6261+GrygG29AEdRC\nSKaZDQ/gNaAWq/2T5iLIQByYaqnRPrEHMZau0cIbBqMnxeOOagasJvrsNdUgTEP7\nU0/CVfTD9iBXExHgvslZW4DBK1SY2PufwTqg6g9uhI1flxMYL/EEwpcbWD3zhSS/\nMkK6Q+p7g1/mOVjWeMjdNG0CgYEA29z+up//fk2KUbWrnKfLu4rCBdZqjEp0JR8Y\n5chI3EIHYw2zs/ayl60m40StRA7xkY5tN/Hqc4LkTudROP7YPO+kHeETM5FfkLWl\ns2v17d/Vx1H3AsOW8JnDkTqbPSlq5TrJc8KX0gPTvBHdqOhV5oINVA1PPbDhKAbO\nNS97pGsCgYEAjbbK0C7sCFBZSyMsRjdU2FibXk0d7KF4FIgSIkuqPBFtjtmdH9av\nxmlzPK7KaLexeVH7rjRtI9mawlOKGmaSkB0ttECH32dA1c/ZEdLpyrPf24vT9LvK\nfyHWmgyb7YkjZjiuI2Fh0LGUMXsH51FeR78yIlkD162NzWTCtGb23pECgYARQqs4\nyYDMUJgQTBvZ445p/b23qZqZwuqVU3in6W5W5FQiIZw+/5oLsEtCQkz779RlIfJP\nFw3Z3afAzgYhXFhriECxG89fGAWRncERceNPtmfZCwVCUUqTPu8MgrZXOd4res7/\n6IH0udowhJKLRRohS4pyU80pwa4bb1VW9ZBWWwKBgFucjmyzxWavHTK/QZ3ELkFF\nOsbPfnoBeroxlZSt5O9YnxWK/Myds4IYU0qnJNd/cqJ2MUFape6of27SWLTIBh9s\nhgS+10c1A7IyiXaufB6I5IpE9JEFwlhBILNlZUW57h0f5aYgj4yhRzsxjizHKYpH\naDk3gg7X2AfdSmSG6rSG\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "firebase-adminsdk-qk5pq@tripzy-code-crafters.iam.gserviceaccount.com",
|
||||
"client_id": "115053398061482683758",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-qk5pq%40tripzy-code-crafters.iam.gserviceaccount.com"
|
||||
}
|
||||
99
Tripzy-main/backend/controllers/activity-controller.js
Normal file
@ -0,0 +1,99 @@
|
||||
const Itinerary = require("../models/itinerary-model");
|
||||
const catchAsync = require("../utils/catch-async");
|
||||
const AppError = require("../utils/app-error");
|
||||
const APIFeatures = require("../utils/api-features");
|
||||
|
||||
exports.createActivity = catchAsync(async (req, res, next) => {
|
||||
const { itineraryId } = req.params;
|
||||
const activity = await Itinerary.findOneAndUpdate(
|
||||
{ _id: itineraryId },
|
||||
{ $push: { activities: req.body } }
|
||||
);
|
||||
|
||||
if (!activity) {
|
||||
return next(new AppError("No itinerary found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: activity,
|
||||
});
|
||||
});
|
||||
|
||||
exports.getAllActivities = catchAsync(async (req, res, next) => {
|
||||
const { itineraryId } = req.params;
|
||||
|
||||
const features = new APIFeatures(
|
||||
Itinerary.find({ _id: itineraryId }),
|
||||
req.query
|
||||
);
|
||||
|
||||
const activities = await features.query;
|
||||
|
||||
if (!activities) {
|
||||
return next(new AppError("No itinerary found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
results: activities.length,
|
||||
data: activities,
|
||||
});
|
||||
});
|
||||
|
||||
exports.getActivityById = catchAsync(async (req, res, next) => {
|
||||
const { itineraryId, activityId } = req.params;
|
||||
const activity = await Itinerary.findOne({
|
||||
_id: itineraryId,
|
||||
"activities._id": activityId,
|
||||
});
|
||||
|
||||
if (!activity) {
|
||||
return next(new AppError("No activity found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: activity,
|
||||
});
|
||||
});
|
||||
|
||||
exports.updateActivityById = catchAsync(async (req, res, next) => {
|
||||
const { itineraryId, activityId } = req.params;
|
||||
const activity = await Itinerary.findOneAndUpdate(
|
||||
{
|
||||
_id: itineraryId,
|
||||
"activities._id": activityId,
|
||||
},
|
||||
req.body,
|
||||
{
|
||||
new: true,
|
||||
runValidators: true,
|
||||
}
|
||||
);
|
||||
|
||||
if (!activity) {
|
||||
return next(new AppError("No activity found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: activity,
|
||||
});
|
||||
});
|
||||
|
||||
exports.deleteActivityById = catchAsync(async (req, res, next) => {
|
||||
const { itineraryId, activityId } = req.params;
|
||||
const activity = await Itinerary.findOneAndDelete({
|
||||
_id: itineraryId,
|
||||
"activities._id": activityId,
|
||||
});
|
||||
|
||||
if (!activity) {
|
||||
return next(new AppError("No activity found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(204).json({
|
||||
status: "success",
|
||||
});
|
||||
});
|
||||
41
Tripzy-main/backend/controllers/coins-controller.js
Normal file
@ -0,0 +1,41 @@
|
||||
const User = require("../models/user-model");
|
||||
const catchAsync = require("../utils/catch-async");
|
||||
const AppError = require("../utils/app-error");
|
||||
const { addCoins } = require("../utils/coins");
|
||||
|
||||
exports.addCoins = catchAsync(async (req, res, next) => {
|
||||
try {
|
||||
const { delta } = req.body;
|
||||
const { user } = req;
|
||||
console.log(user.coins);
|
||||
const result = await addCoins(user, delta);
|
||||
|
||||
if (!result.error) {
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: {
|
||||
coins: user.coins,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return next(new AppError(`Can't add coins`, 400));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
exports.getCoins = catchAsync(async (req, res, next) => {
|
||||
try {
|
||||
const { user } = req;
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: {
|
||||
coins: user.coins,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return next(new AppError(`Can't get coins`, 400));
|
||||
}
|
||||
});
|
||||
53
Tripzy-main/backend/controllers/enrolled-trips-controller.js
Normal file
@ -0,0 +1,53 @@
|
||||
const EnrolledTripsModel = require("../models/enrolled-trips-model");
|
||||
const AppError = require("../utils/app-error");
|
||||
const catchAsync = require("../utils/catch-async");
|
||||
const { addCoins } = require("../utils/coins");
|
||||
|
||||
exports.getAllEnrolledTrips = catchAsync(async (req, res, next) => {
|
||||
const enrolled_trips = await EnrolledTripsModel.find({
|
||||
userId: req.user.id,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: enrolled_trips,
|
||||
});
|
||||
});
|
||||
|
||||
exports.storeEnrolledTrip = catchAsync(async (req, res, next) => {
|
||||
const { tripId } = req.body;
|
||||
const check = await EnrolledTripsModel.findOne({
|
||||
userId: req.user._id,
|
||||
tripId: tripId,
|
||||
});
|
||||
|
||||
if (check) {
|
||||
return next(new AppError("You are already enrolled in this trip", 400));
|
||||
}
|
||||
const enrolledTrip = await EnrolledTripsModel.create({
|
||||
userId: req.user._id,
|
||||
tripId: tripId,
|
||||
});
|
||||
|
||||
addCoins(req.user, 5);
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: enrolledTrip,
|
||||
});
|
||||
});
|
||||
|
||||
exports.getEnrolledUsers = catchAsync(async (req, res, next) => {
|
||||
const enrolled_users = await EnrolledTripsModel.find({
|
||||
tripId: req.params.tripId,
|
||||
})
|
||||
.populate("userId")
|
||||
.populate("tripId");
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: {
|
||||
enrolled_users,
|
||||
},
|
||||
});
|
||||
});
|
||||
85
Tripzy-main/backend/controllers/error-controller.js
Normal file
@ -0,0 +1,85 @@
|
||||
const AppError = require("../utils/app-error");
|
||||
|
||||
const handleCastErrorDB = (error) => {
|
||||
const message = `Invalid value ${error.value} for attribute ${error.path}`;
|
||||
return new AppError(message, 400);
|
||||
};
|
||||
|
||||
const handleDuplicateFieldsDB = (error) => {
|
||||
const value = error.keyValue;
|
||||
const message = `Duplicate field value(s) for ${JSON.stringify(
|
||||
value
|
||||
)}.Please use another value`;
|
||||
return new AppError(message, 400);
|
||||
};
|
||||
|
||||
const handleValidationErrorDB = (error) => {
|
||||
const errors = Object.values(error.errors).map(
|
||||
(ele, index) => `${index + 1}) ${ele.message}`
|
||||
);
|
||||
const message = `Invalid input data for: ${errors.join(". ")}`;
|
||||
return new AppError(message, 400);
|
||||
};
|
||||
|
||||
const handleJWTError = (error) =>
|
||||
new AppError(`Invalid token! Please login in again`, 401);
|
||||
|
||||
const handleTokenExpiredError = (error) =>
|
||||
new AppError(`Your token has been expired! please log in again`, 401);
|
||||
|
||||
const sendErrorDevelopment = (error, req, res) => {
|
||||
return res.status(error.statusCode).json({
|
||||
status: error.status,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
error: error,
|
||||
});
|
||||
};
|
||||
|
||||
const sendErrorProduction = (error, req, res) => {
|
||||
if (error.isOperational) {
|
||||
return res.status(error.statusCode).json({
|
||||
status: error.status,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
|
||||
console.error(`ERROR 💣 ${error}`);
|
||||
return res.status(500).json({
|
||||
status: "error",
|
||||
message: "Something went very wrong :(",
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = (error, req, res, next) => {
|
||||
error.statusCode = error.statusCode || 500;
|
||||
error.status = error.status || "error";
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
sendErrorDevelopment(error, req, res);
|
||||
} else if (process.env.NODE_ENV === "production") {
|
||||
let err = { ...error };
|
||||
err.name = error.name;
|
||||
err.message = error.message;
|
||||
|
||||
if (err.code === 11000) err = handleDuplicateFieldsDB(err);
|
||||
|
||||
switch (err.name) {
|
||||
case "CastError":
|
||||
err = handleCastErrorDB(err);
|
||||
break;
|
||||
case "TokenExpiredError":
|
||||
err = handleTokenExpiredError(err);
|
||||
break;
|
||||
case "ValidationError":
|
||||
err = handleValidationErrorDB(err);
|
||||
break;
|
||||
case "JsonWebTokenError":
|
||||
err = handleJWTError(err);
|
||||
break;
|
||||
}
|
||||
|
||||
sendErrorProduction(err, req, res);
|
||||
}
|
||||
next();
|
||||
};
|
||||
89
Tripzy-main/backend/controllers/itinerary-controller.js
Normal file
@ -0,0 +1,89 @@
|
||||
const Itinerary = require("../models/itinerary-model");
|
||||
const Trip = require("../models/trip-model");
|
||||
const AppError = require("../utils/app-error");
|
||||
const catchAsync = require("../utils/catch-async");
|
||||
|
||||
exports.createItinerary = catchAsync(async (req, res, next) => {
|
||||
const { tripId } = req.params;
|
||||
const itinerary = await Itinerary.create(req.body);
|
||||
const trip = await Trip.findOneAndUpdate(
|
||||
{ _id: tripId },
|
||||
{ itinerary: itinerary._id },
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
|
||||
if (!trip || !itinerary) {
|
||||
return next(new AppError("No trip OR itinerary found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: {
|
||||
itinerary,
|
||||
trip,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
exports.getItinerary = catchAsync(async (req, res, next) => {
|
||||
const { tripId } = req.params;
|
||||
const trip = await Trip.findOne({ _id: tripId });
|
||||
|
||||
if (!trip) {
|
||||
return next(new AppError("No trip found with this Id.", 404));
|
||||
}
|
||||
|
||||
if (!trip.itinerary) {
|
||||
return next(new AppError("No itinerary found with this Id.", 404));
|
||||
}
|
||||
|
||||
const itinerary = await Itinerary.findOne({ _id: trip.itinerary });
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: itinerary,
|
||||
});
|
||||
});
|
||||
|
||||
exports.updateItinerary = catchAsync(async (req, res, next) => {
|
||||
const { tripId } = req.params;
|
||||
const trip = await Trip.findOne({ _id: tripId });
|
||||
|
||||
if (!trip) {
|
||||
return next(new AppError("No trip found with this Id.", 404));
|
||||
}
|
||||
|
||||
if (!trip.itinerary) {
|
||||
return next(new AppError("No itinerary found with this Id.", 404));
|
||||
}
|
||||
|
||||
const itinerary = await Itinerary.findOneAndUpdate(
|
||||
{ _id: trip.itinerary },
|
||||
req.body,
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: itinerary,
|
||||
});
|
||||
});
|
||||
|
||||
exports.deleteItinerary = catchAsync(async (req, res, next) => {
|
||||
const { tripId } = req.params;
|
||||
const trip = await Trip.findOne({ _id: tripId });
|
||||
|
||||
if (!trip) {
|
||||
return next(new AppError("No trip found with this Id.", 404));
|
||||
}
|
||||
|
||||
if (!trip.itinerary) {
|
||||
return next(new AppError("No itinerary found with this Id.", 404));
|
||||
}
|
||||
|
||||
await Itinerary.findOneAndDelete({ _id: trip.itinerary });
|
||||
|
||||
res.status(204).json({
|
||||
status: "success",
|
||||
});
|
||||
});
|
||||
32
Tripzy-main/backend/controllers/suggession-controller.js
Normal file
@ -0,0 +1,32 @@
|
||||
const Suggession = require("../models/suggession-model");
|
||||
const catchAsync = require("../utils/catch-async");
|
||||
const AppError = require("../utils/app-error");
|
||||
|
||||
exports.createSuggession = catchAsync(async (req, res, next) => {
|
||||
const { tripId } = req.params;
|
||||
console.log(req.body);
|
||||
req.body.userId = req.user._id;
|
||||
req.body.status = "pending";
|
||||
|
||||
const suggession = await Suggession.create(req.body);
|
||||
|
||||
if (!suggession) {
|
||||
return next(new AppError("No suggession found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
status: "success",
|
||||
data: suggession,
|
||||
});
|
||||
});
|
||||
|
||||
exports.getAllSuggessions = catchAsync(async (req, res, next) => {
|
||||
const { tripId } = req.params;
|
||||
const suggessions = await Suggession.find({ trip: tripId });
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
results: suggessions.length,
|
||||
data: suggessions,
|
||||
});
|
||||
});
|
||||
129
Tripzy-main/backend/controllers/trip-controller.js
Normal file
@ -0,0 +1,129 @@
|
||||
const Trip = require("../models/trip-model");
|
||||
const EnrolledTripsModel = require("../models/enrolled-trips-model");
|
||||
const catchAsync = require("../utils/catch-async");
|
||||
const AppError = require("../utils/app-error");
|
||||
const APIFeatures = require("../utils/api-features");
|
||||
const { addCoins } = require("../utils/coins");
|
||||
const schedule = require("node-schedule");
|
||||
const User = require("../models/user-model");
|
||||
|
||||
exports.createTrip = catchAsync(async (req, res, next) => {
|
||||
console.log(req.body);
|
||||
try {
|
||||
req.body.createdBy = req.user._id;
|
||||
const trip = await Trip.create(req.body);
|
||||
addCoins(req.user, 50);
|
||||
|
||||
const endDate = new Date(trip.enddate);
|
||||
|
||||
const job = schedule.scheduleJob(endDate, function () {
|
||||
EnrolledTripsModel.find({ tripId: trip._id })
|
||||
.populate("UserId")
|
||||
.then((enrolledUsers) => {
|
||||
enrolledUsers.forEach(async (obj) => {
|
||||
let user = obj.userId;
|
||||
user = await User.findById(user._id);
|
||||
console.log("Updating coins for user: " + user.email);
|
||||
console.log("Previoud coins: " + user.coins);
|
||||
addCoins(user, 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
status: "success",
|
||||
data: trip,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return next(new AppError(`Can't create trip`, 400));
|
||||
}
|
||||
});
|
||||
|
||||
exports.getTripsByName = catchAsync(async (req, res, next) => {
|
||||
const { text } = req.query;
|
||||
const regexTitle = new RegExp(text, "i");
|
||||
console.log(regexTitle);
|
||||
|
||||
const features = new APIFeatures(Trip.find({ title: regexTitle }), req.query);
|
||||
|
||||
const trips = await features.query;
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
results: trips.length,
|
||||
data: trips,
|
||||
});
|
||||
});
|
||||
|
||||
exports.getTripById = catchAsync(async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
const trip = await Trip.findById(id);
|
||||
|
||||
if (!trip) {
|
||||
return next(new AppError("No trip found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: trip,
|
||||
});
|
||||
});
|
||||
|
||||
exports.getAllTrips = catchAsync(async (req, res, next) => {
|
||||
// const features = new APIFeatures(Trip.find(), req.query)
|
||||
// .filter()
|
||||
// .sort()
|
||||
// .fieldLimit()
|
||||
// .pagination();
|
||||
// const trips = await features.query;
|
||||
const trips = await Trip.find().populate("createdBy");
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
results: trips.length,
|
||||
data: trips,
|
||||
});
|
||||
});
|
||||
|
||||
exports.updateTrip = catchAsync(async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
const updatedTrip = await Trip.findByIdAndUpdate(id, req.body, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
});
|
||||
|
||||
if (!updatedTrip) {
|
||||
return next(new AppError("No tour found with this Id.", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: updatedTrip,
|
||||
});
|
||||
});
|
||||
|
||||
exports.deleteTrip = catchAsync(async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
const trip = await Trip.findByIdAndDelete(id);
|
||||
|
||||
if (!trip) {
|
||||
return next(new AppError("No trip found with that Id", 404));
|
||||
}
|
||||
|
||||
res.status(204).json({
|
||||
status: "success",
|
||||
});
|
||||
});
|
||||
|
||||
exports.myTrips = catchAsync(async (req, res, next) => {
|
||||
const trips = await Trip.find({ createdBy: req.user._id }).populate(
|
||||
"createdBy"
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
results: trips.length,
|
||||
data: trips,
|
||||
});
|
||||
});
|
||||
66
Tripzy-main/backend/controllers/user-controller.js
Normal file
@ -0,0 +1,66 @@
|
||||
const User = require("../models/user-model");
|
||||
const AppError = require("../utils/app-error");
|
||||
const catchAsync = require("../utils/catch-async");
|
||||
const admin = require("../config/firebase-config");
|
||||
const { log } = require("console");
|
||||
|
||||
exports.protect = catchAsync(async (req, res, next) => {
|
||||
try {
|
||||
if (!req.headers.authorization) {
|
||||
return next(new AppError("Please sign in to continue", 401));
|
||||
}
|
||||
|
||||
const token = req.headers.authorization.split(" ")[1];
|
||||
|
||||
const decodeValue = await admin.auth().verifyIdToken(token);
|
||||
if (!decodeValue) {
|
||||
res.status(403).json({
|
||||
status: "fail",
|
||||
message: "Access token invalid! Please login again.",
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email: decodeValue.email });
|
||||
req.user = user;
|
||||
return next();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return next(new AppError("Internal Error", 500));
|
||||
}
|
||||
});
|
||||
|
||||
exports.createUser = catchAsync(async (req, res, next) => {
|
||||
const { token } = req.body;
|
||||
|
||||
const decodeValue = await admin.auth().verifyIdToken(token);
|
||||
console.log(decodeValue);
|
||||
|
||||
if (decodeValue) {
|
||||
const user = await User.findOne({ email: decodeValue.email });
|
||||
|
||||
if (user) {
|
||||
return res.status(200).json({
|
||||
status: "success",
|
||||
data: {
|
||||
id: user._id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const newUser = await User.create({
|
||||
name: decodeValue.name,
|
||||
email: decodeValue.email,
|
||||
phoneNumber: null,
|
||||
photo: decodeValue.picture,
|
||||
totalRatings: 0,
|
||||
ratingsCount: 0,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
status: "success",
|
||||
data: {
|
||||
id: newUser["_id"],
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
22
Tripzy-main/backend/middlewares/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
const admin = require("../config/firebase-config");
|
||||
const User = require("../models/user-model");
|
||||
const decodeToken = async (req, res, next) => {
|
||||
const token = req.headers.authorization.split(" ")[1];
|
||||
|
||||
try {
|
||||
const decodeValue = await admin.auth().verifyIdToken(token);
|
||||
console.log(decodeValue);
|
||||
if (decodeValue) {
|
||||
const user = await User.findOne({ email: decodeToken.email });
|
||||
console.log(user);
|
||||
req.user = user;
|
||||
return next();
|
||||
}
|
||||
return res.json({ message: "Unauthorized" });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return res.json({ message: "Internal Error" });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = decodeToken;
|
||||
33
Tripzy-main/backend/models/activity-model.js
Normal file
@ -0,0 +1,33 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const ActivitySchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: [true, "Please enter a title for activity."],
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: [true, "Please enter a description for activity."],
|
||||
},
|
||||
startTime: {
|
||||
type: Date,
|
||||
required: [true, "Please enter a start time for activity."],
|
||||
},
|
||||
endTime: {
|
||||
type: Date,
|
||||
required: [true, "Please enter a end time for activity."],
|
||||
},
|
||||
mapLink: {
|
||||
type: String,
|
||||
required: [true, "Please enter a map link for activity."],
|
||||
},
|
||||
itineraryId: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "Itinerary",
|
||||
required: [true, "Please provide the itineraryId to create activity."],
|
||||
},
|
||||
});
|
||||
|
||||
const Activity = mongoose.model("Activity", ActivitySchema);
|
||||
|
||||
module.exports = { Activity, ActivitySchema };
|
||||
20
Tripzy-main/backend/models/enrolled-trips-model.js
Normal file
@ -0,0 +1,20 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const EnrolledTripsSchema = new mongoose.Schema({
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
},
|
||||
tripId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Trip",
|
||||
required: true,
|
||||
},
|
||||
date: {
|
||||
type: Date,
|
||||
default: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("EnrolledTrips", EnrolledTripsSchema);
|
||||
21
Tripzy-main/backend/models/itinerary-model.js
Normal file
@ -0,0 +1,21 @@
|
||||
const mongoose = require("mongoose");
|
||||
const { ActivitySchema } = require("./activity-model");
|
||||
|
||||
const itinerarySchema = new mongoose.Schema({
|
||||
activities: {
|
||||
type: [ActivitySchema],
|
||||
required: [true, "Please provide list of activities."],
|
||||
},
|
||||
startDate: {
|
||||
type: Date,
|
||||
required: [true, "Please provide start date for the trip!"],
|
||||
},
|
||||
endDate: {
|
||||
type: Date,
|
||||
required: [true, "Please provide end date for the trip!"],
|
||||
},
|
||||
});
|
||||
|
||||
const Itinerary = mongoose.model("Itinerary", itinerarySchema);
|
||||
|
||||
module.exports = Itinerary;
|
||||
26
Tripzy-main/backend/models/suggession-model.js
Normal file
@ -0,0 +1,26 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const SuggessionSchema = new mongoose.Schema({
|
||||
tripId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Trip",
|
||||
required: [true, "Please provide trip id"],
|
||||
},
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: [true, "Please provide user id"],
|
||||
},
|
||||
suggession: {
|
||||
type: String,
|
||||
required: [true, "Please provide suggession"],
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ["pending", "approved", "rejected"],
|
||||
default: "pending",
|
||||
},
|
||||
});
|
||||
|
||||
const Suggession = mongoose.model("Suggession", SuggessionSchema);
|
||||
module.exports = Suggession;
|
||||
48
Tripzy-main/backend/models/trip-model.js
Normal file
@ -0,0 +1,48 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const tripSchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: [true, "Please provide title for the trip."],
|
||||
maxlength: [40, "A trip must have less or equal then 40 characters"],
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: [true, "A tour must have a description"],
|
||||
trim: true,
|
||||
},
|
||||
coverImage: {
|
||||
type: String,
|
||||
required: [true, "A tour must have a cover image"],
|
||||
},
|
||||
createdBy: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "User",
|
||||
required: [true, "Please provide the userId to create trip."],
|
||||
},
|
||||
startDate: {
|
||||
type: Date,
|
||||
required: [true, "Please provide start date for the trip!"],
|
||||
},
|
||||
endDate: {
|
||||
type: Date,
|
||||
required: [true, "Please provide end date for the trip!"],
|
||||
},
|
||||
mapUrl: {
|
||||
type: String,
|
||||
required: [true, "Please provide the trip map url."],
|
||||
},
|
||||
itinerary: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "Itinerary",
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
required: [true, "Please provide the trip price."],
|
||||
},
|
||||
});
|
||||
|
||||
const Trip = mongoose.model("Trip", tripSchema);
|
||||
|
||||
module.exports = Trip;
|
||||
56
Tripzy-main/backend/models/user-model.js
Normal file
@ -0,0 +1,56 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: [true, "Please tell us your name"],
|
||||
trim: true,
|
||||
maxlength: [30, "Name must have less or equal then 30 characters"],
|
||||
validate: {
|
||||
validator: function (value) {
|
||||
return /[A-Za-z]/.test(value);
|
||||
},
|
||||
message: "Please enter a valid name",
|
||||
},
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: [true, "Please tell us your email address"],
|
||||
trim: true,
|
||||
unique: true,
|
||||
lowercase: true,
|
||||
validate: {
|
||||
validator: function (value) {
|
||||
return /^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$/.test(value);
|
||||
},
|
||||
message: "Please enter a valid email address",
|
||||
},
|
||||
},
|
||||
phoneNumber: {
|
||||
type: Number,
|
||||
unique: false,
|
||||
required: false,
|
||||
maxlength: [10, "Name must have less or equal then 10 characters"],
|
||||
minlength: [10, "Name must have less or equal then 10 characters"],
|
||||
},
|
||||
photo: {
|
||||
type: String,
|
||||
default: "default.jpg",
|
||||
trim: true,
|
||||
},
|
||||
coins: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
totalRatings: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
ratingsCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const User = mongoose.model("User", userSchema);
|
||||
module.exports = User;
|
||||
26
Tripzy-main/backend/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "A backend for tripzy",
|
||||
"main": "server.js",
|
||||
"repository": "https://github.com/Nishith-Savla/Tripzy.git",
|
||||
"author": "tripzy",
|
||||
"scripts": {
|
||||
"dev": "nodemon server.js",
|
||||
"start": "NODE_ENV=production node server.js"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"firebase-admin": "^11.5.0",
|
||||
"imagekit": "^4.1.3",
|
||||
"mongoose": "5",
|
||||
"morgan": "^1.10.0",
|
||||
"node-schedule": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.22"
|
||||
}
|
||||
}
|
||||
19
Tripzy-main/backend/routes/activity-route.js
Normal file
@ -0,0 +1,19 @@
|
||||
const router = require("express").Router();
|
||||
|
||||
const activityController = require("../controllers/activity-controller");
|
||||
const userController = require("../controllers/user-controller");
|
||||
|
||||
router.use(userController.protect);
|
||||
|
||||
router
|
||||
.route("/:itineraryId")
|
||||
.post(activityController.createActivity)
|
||||
.get(activityController.getAllActivities);
|
||||
|
||||
router
|
||||
.route("/:itineraryId/:activityId")
|
||||
.get(activityController.getActivityById)
|
||||
.patch(activityController.updateActivityById)
|
||||
.delete(activityController.deleteActivityById);
|
||||
|
||||
module.exports = router;
|
||||
11
Tripzy-main/backend/routes/coins-route.js
Normal file
@ -0,0 +1,11 @@
|
||||
const express = require("express");
|
||||
const CoinsController = require("../controllers/coins-controller");
|
||||
const userController = require("../controllers/user-controller");
|
||||
const router = express.Router();
|
||||
|
||||
router
|
||||
.route("/addCoins")
|
||||
.patch(userController.protect, CoinsController.addCoins);
|
||||
|
||||
router.route("/getCoins").get(userController.protect, CoinsController.getCoins);
|
||||
module.exports = router;
|
||||
16
Tripzy-main/backend/routes/enrolled-trips-route.js
Normal file
@ -0,0 +1,16 @@
|
||||
const express = require("express");
|
||||
const EnrolledTripsController = require("../controllers/enrolled-trips-controller");
|
||||
const userController = require("../controllers/user-controller");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router
|
||||
.route("/")
|
||||
.get(userController.protect, EnrolledTripsController.getAllEnrolledTrips)
|
||||
.post(userController.protect, EnrolledTripsController.storeEnrolledTrip);
|
||||
|
||||
router
|
||||
.route("/getEnrolledUsers/:tripId")
|
||||
.get(EnrolledTripsController.getEnrolledUsers);
|
||||
|
||||
module.exports = router;
|
||||
14
Tripzy-main/backend/routes/itinerary-route.js
Normal file
@ -0,0 +1,14 @@
|
||||
const router = require("express").Router();
|
||||
const itineraryController = require("../controllers/itinerary-controller");
|
||||
const userController = require("../controllers/user-controller");
|
||||
|
||||
router.use(userController.protect);
|
||||
|
||||
router
|
||||
.route("/:tripId")
|
||||
.post(itineraryController.createItinerary)
|
||||
.get(itineraryController.getItinerary)
|
||||
.patch(itineraryController.updateItinerary)
|
||||
.delete(itineraryController.deleteItinerary);
|
||||
|
||||
module.exports = router;
|
||||
12
Tripzy-main/backend/routes/suggession-route.js
Normal file
@ -0,0 +1,12 @@
|
||||
const router = require("express").Router();
|
||||
const suggessionController = require("../controllers/suggession-controller");
|
||||
const userController = require("../controllers/user-controller");
|
||||
|
||||
router.use(userController.protect);
|
||||
|
||||
router
|
||||
.route("/:tourId")
|
||||
.get(suggessionController.getAllSuggessions)
|
||||
.post(suggessionController.createSuggession);
|
||||
|
||||
module.exports = router;
|
||||
20
Tripzy-main/backend/routes/trip-route.js
Normal file
@ -0,0 +1,20 @@
|
||||
const router = require("express").Router();
|
||||
const tripController = require("../controllers/trip-controller");
|
||||
const userController = require("../controllers/user-controller");
|
||||
|
||||
router.route("/myTrips").get(userController.protect, tripController.myTrips);
|
||||
|
||||
router
|
||||
.route("/")
|
||||
.post(userController.protect, tripController.createTrip)
|
||||
.get(tripController.getAllTrips);
|
||||
|
||||
router.route("/search").get(tripController.getTripsByName);
|
||||
|
||||
router
|
||||
.route("/:id")
|
||||
.get(tripController.getTripById)
|
||||
.patch(userController.protect, tripController.updateTrip)
|
||||
.delete(userController.protect, tripController.deleteTrip);
|
||||
|
||||
module.exports = router;
|
||||
8
Tripzy-main/backend/routes/user-route.js
Normal file
@ -0,0 +1,8 @@
|
||||
const express = require("express");
|
||||
const userController = require("../controllers/user-controller");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route("/login").post(userController.createUser);
|
||||
|
||||
module.exports = router;
|
||||
48
Tripzy-main/backend/server.js
Normal file
@ -0,0 +1,48 @@
|
||||
const mongoose = require("mongoose");
|
||||
const dotevn = require("dotenv");
|
||||
|
||||
process.on("uncaughtException", (error) => {
|
||||
console.log(`UNCAUGHT EXCEPTION | SHUTTING DOWN ...`);
|
||||
console.log(error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
dotevn.config({
|
||||
path: "./.env",
|
||||
});
|
||||
|
||||
const database = process.env.MONGO_DATABASE_URL.replace(
|
||||
"<PASSWORD>",
|
||||
process.env.MONGO_DATABASE_PASSWORD
|
||||
);
|
||||
|
||||
mongoose
|
||||
.connect(database, {
|
||||
useNewUrlParser: true,
|
||||
useCreateIndex: true,
|
||||
useUnifiedTopology: true,
|
||||
useFindAndModify: false,
|
||||
})
|
||||
.then(() => console.log(`Database connected successfully!`));
|
||||
|
||||
const app = require("./app");
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`App running on port ${PORT}`);
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (error) => {
|
||||
console.log(`UNHANDLED REJECTION | SHUTTING DOWN ...`);
|
||||
console.log(error.name, error.message);
|
||||
server.close(() => {
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
console.log("👋 SIGTERM RECEIVED! Shutting down server!");
|
||||
server.close(() => {
|
||||
console.log("Process terminated");
|
||||
});
|
||||
});
|
||||
54
Tripzy-main/backend/utils/api-features.js
Normal file
@ -0,0 +1,54 @@
|
||||
class APIFeatures {
|
||||
constructor(query, queryString) {
|
||||
this.query = query;
|
||||
this.queryString = queryString;
|
||||
}
|
||||
|
||||
filter() {
|
||||
const queryObjects = { ...this.queryString };
|
||||
const excludedFields = ["page", "sort", "fields", "limit"];
|
||||
excludedFields.forEach((ele) => delete queryObjects[ele]);
|
||||
|
||||
let queryString = JSON.stringify(queryObjects);
|
||||
queryString = queryString.replace(
|
||||
/\b(gte|gt|lte|lt|eq)\b/g,
|
||||
(match) => `$${match}`
|
||||
);
|
||||
|
||||
this.query = this.query.find(JSON.parse(queryString));
|
||||
return this;
|
||||
}
|
||||
|
||||
sort() {
|
||||
if (this.queryString.sort) {
|
||||
const sortBy = this.queryString.sort.split(",").join(" ");
|
||||
this.query = this.query.sort(sortBy);
|
||||
} else {
|
||||
this.query = this.query.sort("-createdAt");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
fieldLimit() {
|
||||
if (this.queryString.fields) {
|
||||
const fields = this.queryString.fields.split(",").join(" ");
|
||||
this.query = this.query.select(fields);
|
||||
} else {
|
||||
this.query = this.query.select("-__v");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
pagination() {
|
||||
const page = this.queryString.page * 1 || 1;
|
||||
const limit = this.queryString.limit * 1 || 100;
|
||||
|
||||
// page=3&limit=10, 1-10 page 1, 11-20 page 2, 21-30 page 3
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
this.query = this.query.skip(skip).limit(limit);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = APIFeatures;
|
||||
13
Tripzy-main/backend/utils/app-error.js
Normal file
@ -0,0 +1,13 @@
|
||||
class AppError extends Error {
|
||||
constructor(message, statusCode) {
|
||||
super(message);
|
||||
|
||||
this.statusCode = statusCode;
|
||||
this.isOperational = true;
|
||||
this.status = `${this.statusCode}`.startsWith("4") ? "fail" : "error";
|
||||
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppError;
|
||||
7
Tripzy-main/backend/utils/catch-async.js
Normal file
@ -0,0 +1,7 @@
|
||||
const catchAsync = (func) => {
|
||||
return (req, res, next) => {
|
||||
func(req, res, next).catch((error) => next(error));
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = catchAsync;
|
||||
17
Tripzy-main/backend/utils/coins.js
Normal file
@ -0,0 +1,17 @@
|
||||
const User = require("../models/user-model");
|
||||
|
||||
exports.addCoins = async (user, delta) => {
|
||||
try {
|
||||
if (user.coins) {
|
||||
user.coins += delta ? delta : user.coins;
|
||||
} else {
|
||||
user.coins = delta ? delta : user.coins;
|
||||
}
|
||||
|
||||
await user.save();
|
||||
return { coins: user.coins };
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return { error: error };
|
||||
}
|
||||
};
|
||||
0
Tripzy-main/backend/utils/decode-token.js
Normal file
9
Tripzy-main/backend/utils/imagekit-utils.js
Normal file
@ -0,0 +1,9 @@
|
||||
const ImageKit = require("imagekit");
|
||||
|
||||
const imagekit = new ImageKit({
|
||||
publicKey: "public_fCQF5nc2un6BY3+6xE6xp/D/c2g=",
|
||||
privateKey: "private_yVbKoP1UEScfxTdq68Yu/MY6bFw=",
|
||||
urlEndpoint: "https://ik.imagekit.io/fy9xpsxsm",
|
||||
});
|
||||
|
||||
module.exports = imagekit;
|
||||
2331
Tripzy-main/backend/yarn.lock
Normal file
27
Tripzy-main/frontend/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
|
||||
*.env
|
||||
8
Tripzy-main/frontend/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": true,
|
||||
"singleQuote": false,
|
||||
"semi": true,
|
||||
"printWidth": 100,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
23
Tripzy-main/frontend/index.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tripzy - Trips Made Easy</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
29
Tripzy-main/frontend/package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "tripzy-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^4.28.0",
|
||||
"axios": "^1.3.4",
|
||||
"firebase": "^9.19.1",
|
||||
"imagekitio-react": "^2.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-firebase-hooks": "^5.1.1",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"react-toastify": "^9.1.2",
|
||||
"styled-components": "^5.3.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"vite": "^4.2.0"
|
||||
}
|
||||
}
|
||||
48
Tripzy-main/frontend/src/App.jsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import AddActivity from "./components/AddActivity";
|
||||
import Dashboard from "./components/Dashboard";
|
||||
import EditTrip from "./components/EditTrip";
|
||||
import ViewTripDetails from "./components/ViewTripDetails";
|
||||
import Navbar from "./components/Navbar";
|
||||
import Signin from "./components/SignIn";
|
||||
import AuthProvider from "./context/auth";
|
||||
import "./css/palette.css";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
staleTime: 1000 * 30,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/signin" element={<Signin />} />
|
||||
<Route path="/trips">
|
||||
<Route path="new" element={<EditTrip />} />
|
||||
<Route path=":id" element={<ViewTripDetails />} />
|
||||
<Route path=":id/update" element={<EditTrip />} />
|
||||
<Route path=":id/add-activity" element={<AddActivity />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
<ToastContainer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
5
Tripzy-main/frontend/src/api/enrolledUsers.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { axiosInstance } from "../utils";
|
||||
|
||||
export const getEnrolledUsers = async (tripId) => {
|
||||
return axiosInstance.get(`/enrolledTrips/getEnrolledUsers/${tripId}`).then((res) => res.data);
|
||||
};
|
||||
57
Tripzy-main/frontend/src/api/trips.js
Normal file
@ -0,0 +1,57 @@
|
||||
import { axiosInstance } from "../utils";
|
||||
|
||||
export function getTrips() {
|
||||
return axiosInstance.get("/trips").then((res) => res.data);
|
||||
}
|
||||
|
||||
export function getEnrolledTrips() {
|
||||
return axiosInstance.get("/enrolledTrips").then((res) => res.data);
|
||||
}
|
||||
|
||||
export function getTrip(id) {
|
||||
return axiosInstance.get(`trips/${id}`).then((res) => res.data);
|
||||
}
|
||||
|
||||
export function enrollTrip(tripId) {
|
||||
return axiosInstance.post(`/enrolledTrips`, { tripId }).then((res) => res.data);
|
||||
}
|
||||
|
||||
export function searchTrip(tripId, text) {
|
||||
return axiosInstance.get("/trips/search", { params: { tripId, text } }).then((res) => res.data);
|
||||
}
|
||||
|
||||
export function createTrip({
|
||||
title,
|
||||
description,
|
||||
coverImage,
|
||||
startDate,
|
||||
endDate,
|
||||
mapUrl,
|
||||
itineraryId,
|
||||
createdBy,
|
||||
}) {
|
||||
return axiosInstance
|
||||
.post("/trips", {
|
||||
title,
|
||||
description,
|
||||
coverImage,
|
||||
startDate,
|
||||
endDate,
|
||||
mapUrl,
|
||||
itineraryId,
|
||||
createdBy,
|
||||
})
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export function updateTrip(id, params) {
|
||||
return axiosInstance
|
||||
.patch(`/trips/${id}`, {
|
||||
...params,
|
||||
})
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export function deleteTrip(id) {
|
||||
return axois.delete(`trips/${id}`).then((res) => res.data);
|
||||
}
|
||||
7
Tripzy-main/frontend/src/api/user.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { axiosInstance } from "../utils";
|
||||
|
||||
export function postUser({ user, token }) {
|
||||
return axiosInstance.post("auth/login", { user, token }).then((res) => {
|
||||
return res.data;
|
||||
});
|
||||
}
|
||||
BIN
Tripzy-main/frontend/src/assets/fonts/RobotoSlab.ttf
Normal file
BIN
Tripzy-main/frontend/src/assets/icons/search.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Tripzy-main/frontend/src/assets/icons/team.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
Tripzy-main/frontend/src/assets/images/dollar.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
Tripzy-main/frontend/src/assets/images/img1.jpeg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
Tripzy-main/frontend/src/assets/images/img2.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
Tripzy-main/frontend/src/assets/images/img5.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
Tripzy-main/frontend/src/assets/images/img6.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
Tripzy-main/frontend/src/assets/images/img7.webp
Normal file
|
After Width: | Height: | Size: 7.2 MiB |
BIN
Tripzy-main/frontend/src/assets/logo/favicon.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
Tripzy-main/frontend/src/assets/logo/tripzy.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
1
Tripzy-main/frontend/src/assets/logo/tripzy1.svg
Normal file
|
After Width: | Height: | Size: 24 KiB |
1
Tripzy-main/frontend/src/assets/logo/tripzy2.svg
Normal file
|
After Width: | Height: | Size: 24 KiB |
240
Tripzy-main/frontend/src/components.jsx
Normal file
@ -0,0 +1,240 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
export const Section = styled.section`
|
||||
background-color: #f5e9cf;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
export const Background = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
gap: 20px;
|
||||
height: calc(100vh - 130px);
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 40px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h2`
|
||||
font-size: ${(props) => props.fontSize || "34px"};
|
||||
font-weight: 600;
|
||||
color: ${(props) => props.color || "#fff"};
|
||||
text-shadow: 1px 1px #000;
|
||||
`;
|
||||
|
||||
export const Heading = styled.div`
|
||||
font-size: ${(props) => props.fontSize || "30px"};
|
||||
padding: 20px 20px 10px 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0px;
|
||||
`;
|
||||
export const Description = styled.div`
|
||||
font-size: 22px;
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
background-color: #f5e9cf95;
|
||||
padding: 10px;
|
||||
`;
|
||||
|
||||
export const Content = styled.div`
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 15px;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const Itinerary = styled.section`
|
||||
background-color: #f5e9cf10;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
export const Day = styled.div`
|
||||
display: block;
|
||||
background-color: #f5e9cf80;
|
||||
`;
|
||||
|
||||
export const Cards = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 15px;
|
||||
`;
|
||||
|
||||
export const Card = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 50vh;
|
||||
width: 30vh;
|
||||
padding: 20px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 5px;
|
||||
`;
|
||||
|
||||
export const Flex = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.bgColor || "#fbffb180"};
|
||||
flex-direction: ${(props) => props.flexDirection || "row"};
|
||||
gap: ${(props) => props.gap || "5px"};
|
||||
`;
|
||||
|
||||
export const Box = styled.h4`
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
//background-color: ${(props) => props.bgColor || "#4D455D"};
|
||||
color: ${(props) => props.color || "#000"};
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
export const Button = styled.button`
|
||||
padding: 5px 20px;
|
||||
color: #fff;
|
||||
background-color: #fc7300;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 24px;
|
||||
`;
|
||||
|
||||
export const Activities = styled.section`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
`;
|
||||
|
||||
export const Activity = styled.div`
|
||||
margin-left: 40px;
|
||||
background-color: #f5e9cf;
|
||||
padding: 10px;
|
||||
`;
|
||||
|
||||
export const ActivityImage = styled.img`
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
border-radius: 5px;
|
||||
`;
|
||||
|
||||
export const ActivityFlex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
export const ActivityTitle = styled.h4`
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
`;
|
||||
|
||||
export const ActivityDescription = styled.p`
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #555;
|
||||
`;
|
||||
|
||||
export const ActivityDate = styled.p`
|
||||
font-size: 18px;
|
||||
font-weight: 00;
|
||||
background-color: #4d455d;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
`;
|
||||
|
||||
export const Span = styled.span`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
export const MapLink = styled.a`
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
color: #5611df;
|
||||
text-align: ${(props) => props.textAlign || "left"};
|
||||
`;
|
||||
|
||||
export const Suggestion = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
export const Form = styled.form`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
export const TextArea = styled.textarea`
|
||||
padding: 10px;
|
||||
border: 2px solid #333;
|
||||
outline: none;
|
||||
border-radius: 5px;
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
background-color: #f5e9cf;
|
||||
height: 35vh;
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
`;
|
||||
|
||||
export const Message = styled.div`
|
||||
padding: 20px;
|
||||
background-color: #4d455d;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
export const MessageFlex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
`;
|
||||
|
||||
export const MessageTitle = styled.h4`
|
||||
font-size: 18px;
|
||||
color: #bdbcbc;
|
||||
`;
|
||||
|
||||
export const MessageAuthor = styled.p`
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
`;
|
||||
|
||||
export const MessageStatus = styled.p`
|
||||
font-size: 16px;
|
||||
background-color: gray;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
`;
|
||||
82
Tripzy-main/frontend/src/components/AddActivity.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React from "react";
|
||||
import "../css/modal.css";
|
||||
import Navbar from "./Navbar";
|
||||
import "../css/createTrip.css";
|
||||
|
||||
function AddActivity() {
|
||||
return (
|
||||
<>
|
||||
<section id="add-activity" className="d-flex dir-col">
|
||||
<h2 className="text-center trip-title">Add Activity</h2>
|
||||
<div className="d-flex justify-content-center">
|
||||
<form encType="multipart/form-data" className="input-container" method="post">
|
||||
<div className="d-flex g-3">
|
||||
<div className="d-flex dir-col stretch">
|
||||
<label className="input-label" htmlFor="title">
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="activity-name"
|
||||
name="title"
|
||||
placeholder="Title for the trip"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex dir-col stretch">
|
||||
<label className="input-label" htmlFor="image">
|
||||
Upload Image URL
|
||||
</label>
|
||||
<input type="text" id="image" name="coverImage" placeholder="https://" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="input-label" htmlFor="description">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
id="description"
|
||||
cols="auto"
|
||||
rows={3}
|
||||
placeholder="Description for the trip"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex g-3">
|
||||
<div className="d-flex dir-col stretch">
|
||||
<label className="input-label" htmlFor="start-time">
|
||||
Start Time
|
||||
</label>
|
||||
<input type="time" id="start-time" name="startDate" />
|
||||
</div>
|
||||
<div className="d-flex dir-col stretch">
|
||||
<label className="input-label" htmlFor="end-time">
|
||||
End Time
|
||||
</label>
|
||||
<input type="time" id="end-time" name="endDate" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex dir-col stretch mb-1in">
|
||||
<label className="input-label" htmlFor="map-url">
|
||||
Map URL
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="map-url"
|
||||
name="mapUrl"
|
||||
placeholder="Enter the google maps link"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn d-flex align-items-center justify-content-center mx-auto"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddActivity;
|
||||
31
Tripzy-main/frontend/src/components/Card.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import "../css/dashboard.css";
|
||||
|
||||
function Card({ title, createdBy, startDate, endDate, memberCount, coverImage }) {
|
||||
return (
|
||||
<div
|
||||
className="tour-card"
|
||||
style={{
|
||||
backgroundImage: "url(" + coverImage + ")",
|
||||
}}
|
||||
>
|
||||
<div className="card-overlay"></div>
|
||||
<div
|
||||
className=" d-flex dir-col justify-content-between height-100 over-1"
|
||||
style={{ display: "inline-flex" }}
|
||||
>
|
||||
<h2 className="host-name">Created By {createdBy?.name ?? "Anonymous"} </h2>
|
||||
<div className="card-details">
|
||||
<p className="tour-name p-0 m-0">{title}</p>
|
||||
<span className="team">{memberCount}</span>
|
||||
<br />
|
||||
<p className="tour-date">
|
||||
{startDate} - {endDate}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Card;
|
||||
90
Tripzy-main/frontend/src/components/Dashboard.jsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { getEnrolledTrips, getTrips } from "../api/trips";
|
||||
import "../css/dashboard.css";
|
||||
import { formatDate } from "../utils";
|
||||
import Card from "./card";
|
||||
|
||||
function Dashboard() {
|
||||
const allTripsQuery = useQuery({
|
||||
queryKey: ["trips"],
|
||||
queryFn: getTrips,
|
||||
});
|
||||
|
||||
const enrolledTripsQuery = useQuery({
|
||||
queryKey: ["trips", localStorage.getItem("access_token")],
|
||||
enabled: allTripsQuery.data !== undefined,
|
||||
queryFn: getEnrolledTrips,
|
||||
});
|
||||
|
||||
if (allTripsQuery.isLoading && allTripsQuery.fetchStatus !== "idle") {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const { data: allTrips } = allTripsQuery.data;
|
||||
const enrolledTripsQueryData = enrolledTripsQuery.data;
|
||||
let enrolledTrips = null;
|
||||
if (enrolledTripsQueryData) {
|
||||
enrolledTrips = enrolledTripsQueryData.data;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="dashboard">
|
||||
{enrolledTrips?.length ? (
|
||||
<div>
|
||||
<h3 style={{ alignContent: "center" }}>Your trips</h3>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
}}
|
||||
>
|
||||
{enrolledTrips
|
||||
? enrolledTrips.map((trip) => {
|
||||
return (
|
||||
<Card
|
||||
key={trip._id}
|
||||
title={trip.title}
|
||||
createdBy={trip.createdBy}
|
||||
startDate={formatDate(trip.startDate ?? Date.now())}
|
||||
endDate={formatDate(trip.endDate ?? Date.now())}
|
||||
memberCount={trip.memberCount}
|
||||
coverImage={trip.coverImage}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<h3>All trips</h3>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
}}
|
||||
>
|
||||
{allTrips?.map((trip) => {
|
||||
return (
|
||||
<NavLink to={`/trips/${trip._id}`}>
|
||||
<Card
|
||||
key={trip._id}
|
||||
title={trip.title}
|
||||
createdBy={trip.createdBy}
|
||||
startDate={formatDate(trip.startDate)}
|
||||
endDate={formatDate(trip.endDate)}
|
||||
memberCount={trip.memberCount}
|
||||
coverImage={trip.coverImage}
|
||||
/>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
124
Tripzy-main/frontend/src/components/EditTrip.jsx
Normal file
@ -0,0 +1,124 @@
|
||||
import { QueryClient, useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { createTrip, getTrip, updateTrip } from "../api/trips";
|
||||
import "../css/CreateTrip.css";
|
||||
import { formatDate } from "../utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function EditTrip() {
|
||||
const { id } = useParams();
|
||||
|
||||
const tripQuery = useQuery({
|
||||
queryKey: ["trips", id],
|
||||
enabled: !!id,
|
||||
queryFn: () => getTrip(id),
|
||||
});
|
||||
|
||||
const editTripMutation = useMutation({
|
||||
mutationFn: (data) => {
|
||||
if (id) return updateTrip(id, data);
|
||||
else return createTrip();
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(["trips"]);
|
||||
},
|
||||
});
|
||||
|
||||
const formRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
if (tripQuery.data) {
|
||||
const { title, description, startDate, endDate, mapUrl } = tripQuery.data?.data;
|
||||
|
||||
if (!!formRef.current) {
|
||||
formRef.current.title.value = title ?? "";
|
||||
formRef.current.description.value = description ?? "";
|
||||
formRef.current.startDate.value = formatDate(startDate ?? Date.now());
|
||||
formRef.current.endDate.value = formatDate(endDate ?? Date.now());
|
||||
formRef.current.mapUrl.value = mapUrl ?? "";
|
||||
}
|
||||
}
|
||||
}, [tripQuery.data]);
|
||||
|
||||
if (tripQuery.isLoading && tripQuery.fetchStatus !== "idle") return "Loading...";
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(formRef.current);
|
||||
const data = Object.fromEntries(formData);
|
||||
editTripMutation.mutate(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section id="create-trip" className="d-flex dir-col">
|
||||
<h2 className="text-center trip-title">Create Trip</h2>
|
||||
<div className="d-flex justify-content-center">
|
||||
<form className="input-container" ref={formRef} onSubmit={handleSubmit} method="post">
|
||||
<div className="d-flex g-3">
|
||||
<div className="d-flex dir-col stretch">
|
||||
<label className="input-label" htmlFor="title">
|
||||
Title
|
||||
</label>
|
||||
<input type="text" id="trip-name" name="title" placeholder="Title for the trip" />
|
||||
</div>
|
||||
<div className="d-flex dir-col stretch">
|
||||
<label className="input-label" htmlFor="image">
|
||||
Cover Image URL
|
||||
</label>
|
||||
<input type="text" id="image" name="coverImage" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="input-label" htmlFor="description">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
id="description"
|
||||
cols="auto"
|
||||
rows={3}
|
||||
placeholder="Description for the trip"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex g-3">
|
||||
<div className="d-flex dir-col stretch">
|
||||
<label className="input-label" htmlFor="start-date">
|
||||
Start Date
|
||||
</label>
|
||||
<input type="date" id="start-date" name="startDate" />
|
||||
</div>
|
||||
<div className="d-flex dir-col stretch">
|
||||
<label className="input-label" htmlFor="end-date">
|
||||
end Date
|
||||
</label>
|
||||
<input type="date" id="end-date" name="endDate" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex dir-col stretch mb-1in">
|
||||
<label className="input-label" htmlFor="map-url">
|
||||
Map URL
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="map-url"
|
||||
name="mapUrl"
|
||||
placeholder="Enter the google maps link"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn d-flex align-items-center justify-content-center mx-auto"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditTrip;
|
||||
132
Tripzy-main/frontend/src/components/Navbar.jsx
Normal file
@ -0,0 +1,132 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { NavLink, useNavigate } from "react-router-dom";
|
||||
import Dollar from "../assets/images/dollar.png";
|
||||
import trpizyLogo from "../assets/logo/tripzy.png";
|
||||
import { AuthContext } from "../context/auth";
|
||||
import { logOut } from "../firebase/firebase";
|
||||
|
||||
const CoinChip = ({ coins }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
maxWidth: "fit-content",
|
||||
|
||||
paddingTop: "0.5rem",
|
||||
paddingBottom: "0.5rem",
|
||||
paddingRight: "1.25rem",
|
||||
paddingLeft: "1.25rem",
|
||||
// maxHeight: "100px",
|
||||
gap: "5px",
|
||||
borderWidth: "1px",
|
||||
borderStyle: "solid",
|
||||
borderColor: "black",
|
||||
borderRadius: 100,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<img
|
||||
style={{
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
}}
|
||||
src={Dollar}
|
||||
alt="coin"
|
||||
/>
|
||||
</div>
|
||||
<div>{coins}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function Navbar() {
|
||||
const { user, loading } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
if (!user && !loading) {
|
||||
navigate("/signin");
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const Display = (props) => {
|
||||
if (user) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<CoinChip coins={user?.coins || 23} />
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
padding: "5px 20px",
|
||||
color: "#000",
|
||||
backgroundColor: "#f1a90d",
|
||||
height: "50px",
|
||||
fontSize: "24px",
|
||||
marginTop: "5px",
|
||||
marginLeft: "10px",
|
||||
}}
|
||||
onClick={() => {
|
||||
logOut();
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<header className="d-flex align-items-center g-3">
|
||||
<img src={trpizyLogo} width="60px" alt="" />
|
||||
<h1 className="secondary-font page-title larger-2">Tripzy</h1>
|
||||
|
||||
<ul
|
||||
className="navbar-nav me-auto mb-2 mb-lg-0 d-flex justify-content-evenly"
|
||||
style={{ flexDirection: "row", width: "20%" }}
|
||||
>
|
||||
<li className="nav-item">
|
||||
<NavLink className="nav-link active" aria-current="page" to="/">
|
||||
Home
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink className="nav-link" to="/trips/new">
|
||||
Host Trip
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form className="d-flex">
|
||||
<input
|
||||
className="form-control me-2"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
aria-label="Search"
|
||||
style={{ width: "500px", margin: "10px 30px 0px 10px" }}
|
||||
/>
|
||||
|
||||
{/* <button
|
||||
style={{
|
||||
padding: "5px 20px",
|
||||
color: "#fff",
|
||||
backgroundColor: "#002B5B",
|
||||
height: "50px",
|
||||
fontSize: "24px",
|
||||
marginTop: "5px",
|
||||
}}
|
||||
type="submit"
|
||||
>
|
||||
Search
|
||||
</button> */}
|
||||
<Display />
|
||||
</form>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Navbar;
|
||||
64
Tripzy-main/frontend/src/components/SignIn.jsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import searchImage from "../assets/icons/search.png";
|
||||
import img1 from "../assets/images/img1.jpeg";
|
||||
import img2 from "../assets/images/img2.jpg";
|
||||
import img6 from "../assets/images/img6.jpg";
|
||||
import { AuthContext, useAuth } from "../context/auth";
|
||||
import "../css/signin.css";
|
||||
import "../css/style.css";
|
||||
import "../css/util.css";
|
||||
|
||||
function Signup() {
|
||||
const auth = useAuth();
|
||||
const { user, loading } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
if (user && !loading) {
|
||||
navigate("/", { replace: true });
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<section id="login-page">
|
||||
<div className="d-flex justify-content-between p-5 container">
|
||||
<div className="fb-48 d-flex justify-content-center mt-5">
|
||||
{/* The carousel part */}
|
||||
<div id="carouselExampleControls" className="carousel slide" data-bs-ride="carousel">
|
||||
<div className="carousel-inner">
|
||||
<div className="carousel-item">
|
||||
<img src={img1} className="d-block w-100" alt="..." />
|
||||
</div>
|
||||
<div className="carousel-item active">
|
||||
<img src={img2} className="d-block w-100" alt="..." />
|
||||
</div>
|
||||
<div className="carousel-item">
|
||||
<img src={img6} className="d-block w-100" alt="..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="vertical-line" />
|
||||
<div className="fb-48 d-flex justify-content-center">
|
||||
{/* The Form Part */}
|
||||
<div className="input-container d-flex dir-col mt-5 align-items-center g-3">
|
||||
<h3 className="text-center primary-font larger p-0 m-0 dark-purple">Sign in</h3>
|
||||
<input type="text" placeholder="Email" />
|
||||
<input type="password" placeholder="Password" />
|
||||
<button className="btn btn-primary">Sign in</button>
|
||||
<p className="large primary-font">---------- or ----------</p>
|
||||
{/* or signin with google */}
|
||||
<div className="d-flex justify-content-center">
|
||||
<button className="btn btn-google d-flex align-items-center g-2" onClick={auth.login}>
|
||||
<span>Sign in with Google</span>
|
||||
<img src={searchImage} width="30px" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default Signup;
|
||||
186
Tripzy-main/frontend/src/components/ViewTripDetails.jsx
Normal file
@ -0,0 +1,186 @@
|
||||
import React, { useRef } from "react";
|
||||
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getTrip } from "../api/trips";
|
||||
import {
|
||||
Activities,
|
||||
Activity,
|
||||
ActivityDate,
|
||||
ActivityDescription,
|
||||
ActivityFlex,
|
||||
ActivityImage,
|
||||
ActivityTitle,
|
||||
Background,
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Content,
|
||||
Day,
|
||||
Description,
|
||||
Flex,
|
||||
Form,
|
||||
Heading,
|
||||
Itinerary,
|
||||
MapLink,
|
||||
Message,
|
||||
MessageAuthor,
|
||||
MessageFlex,
|
||||
MessageStatus,
|
||||
MessageTitle,
|
||||
Section,
|
||||
Span,
|
||||
Suggestion,
|
||||
TextArea,
|
||||
Title,
|
||||
} from "../components";
|
||||
import { axiosInstance, formatDate } from "../utils";
|
||||
|
||||
const ViewTripDetails = () => {
|
||||
const { id } = useParams();
|
||||
const sug = useRef(null);
|
||||
|
||||
const tripQuery = useQuery({
|
||||
queryKey: ["trips", id],
|
||||
queryFn: () => getTrip(id),
|
||||
});
|
||||
|
||||
function createSuggestion(id, dt) {
|
||||
return axiosInstance
|
||||
.post(`suggestions/${id}`, {
|
||||
tripId: id,
|
||||
dt,
|
||||
})
|
||||
.then((res) => res.dt);
|
||||
}
|
||||
|
||||
const enrollTripMutation = useMutation({
|
||||
mutationFn: () => {
|
||||
return enrollTrip(id);
|
||||
},
|
||||
onSuccess: () => {
|
||||
// queryClient.invalidateQueries(["trips"]);
|
||||
console.log("Success");
|
||||
},
|
||||
});
|
||||
|
||||
if (tripQuery.isLoading && tripQuery.fetchStatus !== "idle") {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const { data } = tripQuery.data;
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
// const data = Object.fromEntries(formData);
|
||||
let dt = {
|
||||
suggestion: sug?.current.value,
|
||||
};
|
||||
createSuggestionMutate.mutate(id, dt);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<Background
|
||||
style={{
|
||||
backgroundImage: "url(" + data?.coverImage + ")",
|
||||
}}
|
||||
>
|
||||
<Title>{data?.title}</Title>
|
||||
<Button
|
||||
onClick={() => {
|
||||
enrollTripMutation.mutate();
|
||||
}}
|
||||
>
|
||||
Join Now
|
||||
</Button>
|
||||
</Background>
|
||||
<Content>
|
||||
<Description>
|
||||
<Heading>Description:</Heading>
|
||||
<br />
|
||||
{data?.description}
|
||||
</Description>
|
||||
<hr />
|
||||
<Flex flexDirection="column" justify-content="left">
|
||||
<Flex flexDirection="row">
|
||||
<Box>Begins on</Box>
|
||||
<Box>{formatDate(data?.startDate ?? Date.now())}</Box>
|
||||
<Box>till</Box>
|
||||
<Box>{formatDate(data?.endDate ?? Date.now())}</Box>
|
||||
</Flex>
|
||||
<Flex flexDirection="row" bgColor="#FC730080">
|
||||
<Box>Members enrolled: </Box>
|
||||
<Box>{data?.memberCount ?? 45}</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<MapLink href={data?.mapUrl} target="_blank" textAlign="center">
|
||||
Open in map
|
||||
</MapLink>
|
||||
</Content>
|
||||
</Section>
|
||||
<Itinerary>
|
||||
<Title color="#000">Itinerary</Title>
|
||||
<Day>
|
||||
<Heading font-size="20px">Day 1</Heading>
|
||||
<Activities>
|
||||
<Activity>
|
||||
<Flex>
|
||||
<ActivityDate>
|
||||
Start <Span>03-03-2023</Span>
|
||||
</ActivityDate>
|
||||
<ActivityDate>
|
||||
End <Span>05-03-2023</Span>
|
||||
</ActivityDate>
|
||||
</Flex>
|
||||
<Flex gap="10px">
|
||||
<ActivityImage src="https://images.unsplash.com/photo-1543731068-7e0f5beff43a" />
|
||||
<ActivityFlex>
|
||||
<ActivityTitle>Activity Title</ActivityTitle>
|
||||
<ActivityDescription>
|
||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quos facere iure
|
||||
eligendi!
|
||||
</ActivityDescription>
|
||||
</ActivityFlex>
|
||||
</Flex>
|
||||
<MapLink href="https://maps.google.com/">Open in Map</MapLink>
|
||||
</Activity>
|
||||
</Activities>
|
||||
</Day>
|
||||
</Itinerary>
|
||||
|
||||
<Suggestion>
|
||||
<div className="left-side">
|
||||
<Title color="#000">Suggestions</Title>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<TextArea ref={sug} placeholder="Type your suggestion..." rows={5} />
|
||||
<Button type="submit">Submit</Button>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<div className="right-side">
|
||||
<Title color="#000">Approvals</Title>
|
||||
<Container>
|
||||
<MessageFlex>
|
||||
<Message>
|
||||
<Flex bgColor="transparent">
|
||||
<MessageAuthor>Hello World</MessageAuthor>
|
||||
<MessageStatus>PENDING</MessageStatus>
|
||||
</Flex>
|
||||
<MessageTitle>Can we bring something in the trip.</MessageTitle>
|
||||
<Flex bgColor="transparent">
|
||||
<Button>Accept</Button>
|
||||
<Button>Decline</Button>
|
||||
</Flex>
|
||||
</Message>
|
||||
</MessageFlex>
|
||||
</Container>
|
||||
</div>
|
||||
</Suggestion>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewTripDetails;
|
||||
17
Tripzy-main/frontend/src/config/firebase-config.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Import the functions you need from the SDKs you need
|
||||
import { initializeApp } from "firebase/app";
|
||||
// TODO: Add SDKs for Firebase products that you want to use
|
||||
// https://firebase.google.com/docs/web/setup#available-libraries
|
||||
|
||||
// Your web app's Firebase configuration
|
||||
const firebaseConfig = {
|
||||
apiKey: import.meta.env.VITE_apiKey,
|
||||
authDomain: import.meta.env.VITE_authDomain,
|
||||
projectId: import.meta.env.VITE_projectId,
|
||||
storageBucket: import.meta.env.VITE_storageBucket,
|
||||
messagingSenderId: import.meta.env.VITE_messagingSenderId,
|
||||
appId: import.meta.env.VITE_appId,
|
||||
};
|
||||
|
||||
// Initialize Firebase
|
||||
export const app = initializeApp(firebaseConfig);
|
||||
42
Tripzy-main/frontend/src/context/auth.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { createContext, useContext, useState, useEffect } from "react";
|
||||
import { loginWithGoogle, auth } from "../firebase/firebase";
|
||||
import { postUser } from "../api/user";
|
||||
import { useAuthState } from "react-firebase-hooks/auth";
|
||||
import { notify } from "../utils";
|
||||
|
||||
export const AuthContext = createContext();
|
||||
|
||||
const AuthProvider = (props) => {
|
||||
const [user, setUser] = useState(null);
|
||||
const [_, loading, error] = useAuthState(auth);
|
||||
useEffect(() => {
|
||||
auth.onAuthStateChanged(setUser);
|
||||
}, []);
|
||||
|
||||
const login = async () => {
|
||||
const { user, error } = await loginWithGoogle();
|
||||
|
||||
if (!user && !loading) {
|
||||
console.log(error);
|
||||
if (error?.code !== "auth/cancelled-popup-request") {
|
||||
notify(error.message, "error");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await postUser({ token: user.accessToken });
|
||||
localStorage.setItem("access_token", user.accessToken);
|
||||
console.log(localStorage.getItem("access_token"));
|
||||
};
|
||||
|
||||
const value = { user, login, loading, error };
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value} {...props}>
|
||||
{props.children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAuth = () => useContext(AuthContext);
|
||||
export default AuthProvider;
|
||||
65
Tripzy-main/frontend/src/css/createTrip.css
Normal file
@ -0,0 +1,65 @@
|
||||
/* This file will contain styles for the create trip form */
|
||||
|
||||
#create-trip, #add-activity {
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-beige);
|
||||
}
|
||||
|
||||
.trip-title {
|
||||
font-family: var(--secondary-font);
|
||||
font-size: 3rem;
|
||||
letter-spacing: 1mm;
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
color: var(--dark-purple);
|
||||
text-shadow: 0px 0px 5px rgba(77, 69, 93, 0.75);
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* justify-content: center; */
|
||||
align-items: stretch;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
border: 1px solid var(--light-cyan);
|
||||
font-family: var(--para-font);
|
||||
border-radius: 5px;
|
||||
font-size: 1.5rem;
|
||||
transition: border 250ms ease-in-out;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border: 3px solid var(--light-cyan);
|
||||
}
|
||||
|
||||
input[type="text"]::placeholder,
|
||||
textarea::placeholder {
|
||||
transition: transform 500ms ease, opacity 500ms ease;
|
||||
}
|
||||
|
||||
input[type="text"]:focus::placeholder {
|
||||
transform: translate(50%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-family: var(--secondary-font);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1mm;
|
||||
color: var(--dark-purple);
|
||||
text-shadow: 0px 0px 5px rgba(77, 69, 93, 0.75);
|
||||
}
|
||||
85
Tripzy-main/frontend/src/css/dashboard.css
Normal file
@ -0,0 +1,85 @@
|
||||
/* This page will only maintain styles for dashboard */
|
||||
#dashboard{
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-beige);
|
||||
}
|
||||
|
||||
.tour-card{
|
||||
width: 400px;
|
||||
height:350px ;
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
margin: .5em 1.5em;
|
||||
display:inline-block;
|
||||
transition: box-shadow 300ms ease-out;
|
||||
/* background-image: url('https://travellersworldwide.com/wp-content/uploads/2022/06/shutterstock_712575202.jpg.webp'); */
|
||||
background-size: cover;
|
||||
|
||||
}
|
||||
|
||||
.card-overlay{
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.822) 20%, transparent 80%);
|
||||
transition: background 400ms ease-in-out;
|
||||
}
|
||||
|
||||
.over-1{
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.host-name{
|
||||
font-family: var(--secondary-font);
|
||||
font-weight: 900;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
padding: 1em .5em;
|
||||
color: var(--dark-purple);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.card-details{
|
||||
padding-left: .5em;
|
||||
height: 35%;
|
||||
}
|
||||
|
||||
.tour-name{
|
||||
font-family: var(--primary-font-bold);
|
||||
font-weight: 900;
|
||||
font-size: 2rem;
|
||||
/* padding: 1em; */
|
||||
color: var(--bg-yellow);
|
||||
}
|
||||
|
||||
.team{
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif, Courier, monospace;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
display: inline-block;
|
||||
margin-top: .5em;
|
||||
color: var(--bg-beige);
|
||||
}
|
||||
|
||||
.team::after{
|
||||
content: "";
|
||||
background-image: url('../assets/icons/team.png');
|
||||
background-size: 25px;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 15px;
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.tour-date{
|
||||
font-family: var(--secondary-font);
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
display: inline-block;
|
||||
margin-top: .5em;
|
||||
color: var(--bg-beige);
|
||||
}
|
||||
|
||||
0
Tripzy-main/frontend/src/css/modal.css
Normal file
74
Tripzy-main/frontend/src/css/palette.css
Normal file
@ -0,0 +1,74 @@
|
||||
@font-face {
|
||||
font-family: 'Philosopher-Reg';
|
||||
src: url('../assets/fonts/Philosopher/Philosopher-Regular.ttf');
|
||||
/*
|
||||
font-family: 'Philosopher-Ital';
|
||||
src: url('../fonts/Philosopher/Philosopher-Italic.ttf'); */
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Philosopher-Bold';
|
||||
src: url('../assets/fonts/Philosopher/Philosopher-Bold.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'SalmaPro';
|
||||
src: url('../assets/fonts/SalmaPro/SalmaPro-Medium.otf');
|
||||
/* font-family: 'SalmaPro-Narr';
|
||||
src: url('../fonts/SalmaPro/SalmaPro-MediumNarrow.otf'); */
|
||||
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'SimplyMono';
|
||||
src: url('../assets/fonts/SimplyMono/SimplyMono-Book.ttf');
|
||||
/* font-family: 'SimplyMono-Ital';
|
||||
src: url('../fonts/SimplyMono/SimplyMono-BookOblique.ttf'); */
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'RobotoSlab';
|
||||
src: url('../assets/fonts/RobotoSlab.ttf');
|
||||
}
|
||||
|
||||
|
||||
:root{
|
||||
/* Color scheme */
|
||||
--dark-purple : #4D455D;
|
||||
--dark-purple-blur : #4d455d85;
|
||||
--sky-pink : #E96479;
|
||||
--bg-beige : #F5E9CF;
|
||||
--bg-yellow : #fbffb1;
|
||||
--light-cyan : #7DB9B6;
|
||||
--orange : #FC7300;
|
||||
--florecent : #BFDB38;
|
||||
--dark-florecent : #1F8A70;
|
||||
--light-pink : #FFACAC;
|
||||
--dark-blue : #002B5B;
|
||||
/* font families */
|
||||
|
||||
--primary-font : 'Philosopher-Reg';
|
||||
--primary-font-bold : 'Philosopher-Bold';
|
||||
--secondary-font : 'SalmaPro';
|
||||
--para-font : 'RobotoSlab';
|
||||
--title-font : 'SimplyMono';
|
||||
}
|
||||
|
||||
.primary-font{
|
||||
font-family: var(--primary-font);
|
||||
}
|
||||
.primary-font-bold{
|
||||
font-family: var(--primary-font-bold);
|
||||
}
|
||||
|
||||
.secondary-font{
|
||||
font-family: var(--secondary-font);
|
||||
}
|
||||
|
||||
.para-font{
|
||||
font-family: var(--para-font);
|
||||
}
|
||||
|
||||
.title-font{
|
||||
font-family: var(--title-font);
|
||||
}
|
||||
|
||||
.dark-purple{
|
||||
color: var(--dark-purple);
|
||||
}
|
||||
41
Tripzy-main/frontend/src/css/signin.css
Normal file
@ -0,0 +1,41 @@
|
||||
/* This page will only maintain styles for login-page */
|
||||
|
||||
#login-page{
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-beige);
|
||||
}
|
||||
|
||||
.vertical-line{
|
||||
background-color: var(--dark-purple);
|
||||
width: 4px;
|
||||
height: 77vh;
|
||||
}
|
||||
|
||||
.input-container{
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="password"]{
|
||||
width: 100%;
|
||||
padding: .5em;
|
||||
margin: 0.5em 0;
|
||||
border: 1px solid var(--light-cyan);
|
||||
font-family: var(--para-font);
|
||||
border-radius: 5px;
|
||||
font-size: 1.5rem;
|
||||
transition: border 250ms ease-in-out;
|
||||
}
|
||||
|
||||
input:focus{
|
||||
outline: none;
|
||||
border: 5px solid var(--light-cyan);
|
||||
}
|
||||
|
||||
input::placeholder{
|
||||
transition: transform 500ms ease, opacity 500ms ease;
|
||||
}
|
||||
|
||||
input:focus::placeholder{
|
||||
transform: translate(50%);
|
||||
opacity: 0;
|
||||
}
|
||||
86
Tripzy-main/frontend/src/css/style.css
Normal file
@ -0,0 +1,86 @@
|
||||
/* this css will maintain styles common for all documents */
|
||||
|
||||
body, html{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
*,*::after,*::before{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* styles under header */
|
||||
|
||||
header{
|
||||
padding: 1em 3em ;
|
||||
background-color: var(--light-cyan);
|
||||
}
|
||||
|
||||
.page-title{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
color: var(--dark-purple);
|
||||
text-shadow: 0px 0px 5px rgba(77, 69, 93, 0.75);
|
||||
}
|
||||
|
||||
.btn{
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 5px;
|
||||
font-family: var(--primary-font);
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
background-color: var(--light-cyan);
|
||||
color: var(--dark-purple);
|
||||
border : 5px outset var(--dark-purple-blur);
|
||||
transition: background-color 250ms ease-in-out;
|
||||
}
|
||||
.btn:active{
|
||||
border : 5px inset var(--dark-purple-blur);
|
||||
}
|
||||
|
||||
/* Carousel styles */
|
||||
.carousel{
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
background-color: var(--bg-beige);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.carousel-inner{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.carousel.slide img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.carousel-item{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transition: opacity 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.carousel-item.active{
|
||||
opacity: 1;
|
||||
}
|
||||
174
Tripzy-main/frontend/src/css/util.css
Normal file
@ -0,0 +1,174 @@
|
||||
/* This file maintains the styles that are unnecessarily repeated*/
|
||||
.smaller {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.small {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.medium {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.large {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.larger {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
.larger-2 {
|
||||
font-size: 2.8rem;
|
||||
}
|
||||
.text-start {
|
||||
text-align: start;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-end {
|
||||
text-align: end;
|
||||
}
|
||||
.text-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.f-bold {
|
||||
font-weight: bolder;
|
||||
}
|
||||
.border {
|
||||
border: 1px solid black;
|
||||
}
|
||||
.b-radius-subtle {
|
||||
border-radius: 5px;
|
||||
}
|
||||
.of-v {
|
||||
overflow: visible;
|
||||
}
|
||||
.of-s {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.of-h {
|
||||
overflow: hidden;
|
||||
}
|
||||
.d-none {
|
||||
display: none;
|
||||
}
|
||||
.d-block {
|
||||
display: block;
|
||||
}
|
||||
.d-in-block {
|
||||
display: inline-block;
|
||||
}
|
||||
.d-flex {
|
||||
display: flex;
|
||||
}
|
||||
.dir-row-rev {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.dir-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.justify-content-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.justify-content-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
.justify-content-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.align-items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.align-items-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
.align-items-top {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.fb-100 {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
.fb-80 {
|
||||
flex-basis: 80%;
|
||||
}
|
||||
.fb-70 {
|
||||
flex-basis: 70%;
|
||||
}
|
||||
.fb-60 {
|
||||
flex-basis: 60%;
|
||||
}
|
||||
.fb-50 {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
.fb-48 {
|
||||
flex-basis: 48%;
|
||||
}
|
||||
.fb-40 {
|
||||
flex-basis: 40%;
|
||||
}
|
||||
.fb-30 {
|
||||
flex-basis: 30%;
|
||||
}
|
||||
.fb-20 {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
.g-2{
|
||||
gap: 0.5em;
|
||||
}
|
||||
.g-3 {
|
||||
gap: 1em;
|
||||
}
|
||||
.g-4 {
|
||||
gap: 1.5em;
|
||||
}
|
||||
.g-6 {
|
||||
gap: 3em;
|
||||
}
|
||||
.g-1in {
|
||||
gap: 1in;
|
||||
}
|
||||
.stretch {
|
||||
width: 100%;
|
||||
}
|
||||
.height-100 {
|
||||
height: 100%;
|
||||
}
|
||||
.bg-transparent {
|
||||
background: transparent;
|
||||
}
|
||||
.bg-black {
|
||||
background: black;
|
||||
}
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.ms-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
.me-auto {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.p-5 {
|
||||
padding: 2.5em;
|
||||
}
|
||||
.p-0{
|
||||
padding:0;
|
||||
}
|
||||
.m-0{
|
||||
margin:0;
|
||||
}
|
||||
.mt-1in {
|
||||
margin-top: 1in;
|
||||
}
|
||||
.mt-5 {
|
||||
margin-top: 2.5em;
|
||||
}
|
||||
20
Tripzy-main/frontend/src/firebase/firebase.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { GoogleAuthProvider, getAuth, signInWithPopup, signOut } from "firebase/auth";
|
||||
|
||||
const provider = new GoogleAuthProvider();
|
||||
provider.addScope("https://www.googleapis.com/auth/contacts.readonly");
|
||||
export const auth = getAuth();
|
||||
|
||||
export const loginWithGoogle = async () => {
|
||||
try {
|
||||
const response = await signInWithPopup(auth, provider);
|
||||
|
||||
const user = response.user;
|
||||
return { user };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
};
|
||||
|
||||
export const logOut = async () => {
|
||||
signOut(auth);
|
||||
};
|
||||
65
Tripzy-main/frontend/src/index.css
Normal file
@ -0,0 +1,65 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
background-color: #f5e9cf;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #f5e9cf;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f5e9cf;
|
||||
}
|
||||
}
|
||||
11
Tripzy-main/frontend/src/main.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
import "./config/firebase-config";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
27
Tripzy-main/frontend/src/utils.js
Normal file
@ -0,0 +1,27 @@
|
||||
import axios from "axios";
|
||||
import { toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
|
||||
export const notify = (message, type) => {
|
||||
toast(message, {
|
||||
position: toast.POSITION.TOP_RIGHT,
|
||||
autoClose: 5000,
|
||||
pauseOnHover: true,
|
||||
type: type,
|
||||
});
|
||||
};
|
||||
|
||||
export const axiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_backendURL,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
|
||||
"ngrok-skip-browser-warning": true,
|
||||
// timeout: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
// format in yyyy-MM-dd
|
||||
export const formatDate = (date) => {
|
||||
console.log(date);
|
||||
return new Date(date).toISOString().slice(0, 10);
|
||||
};
|
||||
7
Tripzy-main/frontend/vite.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||