star rating value from React to Mongodb - node.js

I am working to show the rating for every college that is shown to user based on user score. Suppose user A gives college 1 as rating 5, then the rating will be send to mongodb database
In database I have made a collection named rating
ratingSchema = mongoose.Schema({
rating: Number,
userId: {
ref: 'users',
type: mongoose.SchemaTypes.ObjectId
// a string or whatever your id is
},
collegeId: {
ref: 'colleges',
type: mongoose.SchemaTypes.ObjectId
},
})
const Rating = mongoose.model('rating', ratingSchema);
The query written in nodeJS file is:
Rating
.findOne({})
.populate('userId')
.populate('collegeId');
How to post the rating from react to mongodb.
the function made in react is :
component/Report.js
const StarRating = (props) => {
console.log(props);
return (
<div>
{Array(5)
.fill(0)
.map((_, idx) => (
<label key={idx}>
<input
type="radio"
name="rating"
onChange={handleRate}
value={props.ratingValue}
checked={idx === props.ratingValue}
/>
<FaStar color={idx < 3 ? "#01af93" : "#bbb"} />
</label>
))}
</div>
);
};
const Report = (props) => {
const { advices } = useSelector((state) => state.advice);
const [rate, setRating] = useState(null);
useEffect(() => {
if (!advices) {
dispatch(fetchAdvices(history));
}
});
useEffect(() => {
async function fetchRate() {
try {
const { rating } = await api.get(paths.FETCH_RATING);
console.log(rating + "user rating");
} catch(error) {
console.log(error);
}
};
fetchRate();
}, []);
const handleRate = async() => {
const rate = await api.post(paths.UPDATE_RATING, {rating:rate});
props.setRating(rate)
}
return (
<>
<Container>
<Grid>
<Fragment>
<Grid >
<Card>
<CardContent><> <div>
<StarRating setRating={(val) => setRate(val)} ratingValue={rate} />
</div></></CardContent>
</Card>
</Grid>
</>
)};
i want to set rating for college and next time when user sees the order of colleges will be in the highest ranking to lowest ranking.

const router = express.Router();
router.post('/ratings', async (req, res) => {
const { rating, collegeId } = req.body;
// just an example to userId, depends on your authentication method
const userId = req.user.id
// example validation
if (!rating || !collegeId) {
return res.status(400).send({ message: 'bad request.' });
}
try {
const newRating = new Rating({rating, collegeId, userId})
await newRating.save()
return res.status(201).json({message:"success", rating:newRating})
} catch (error) {
res.status(500).send({ message: 'database error' });
}
});
then you can axios.post("yourApiUrl/ratings", {rating: 5, collegeId:123456})
router.get('/ratings', async (req, res) => {
try {
// maybe you need modify I didn't memorize mongoose methods
const ratings = await Rating.find({userId}
.populate('userId')
.populate('collegeId').sort({rating:1});
return res.status(201).json({message:"success", ratings})
} catch (error) {
res.status(500).send({ message: 'database error' });
}
});
this route gives ordered by user ratings.

Related

How to filter posts by category

I tried to filter posts by category but it's not working on frontend
I want when a user clicks on a particular category to get posts in that category
this is my backend (NODEJS)
exports.getMovies = async (req, res) => {
const { pageNo = 0, limit = 10 } = req.query;
// filter category
let filter = {};
if (req.query.categories) {
filter = { category: req.query.categories.split(",") };
}
const movies = await Movie.find(filter)
.populate("category comments")
.sort({ createdAt: -1 })
.skip(parseInt(pageNo) * parseInt(limit))
.limit(parseInt(limit));
const results = movies.map((movie) => ({
id: movie._id,
title: movie.title,
poster: movie.poster?.url,
responsivePosters: movie.poster?.responsive,
category: movie.category,
comments: movie.comments,
genres: movie.genres,
status: movie.status,
}));
res.json({ movies: results });
};
The front end API
export const getMovies = async (pageNo, limit, filter) => {
const token = getToken();
try {
const { data } = await client(
`/movie/movies?pageNo=${pageNo}&limit=${limit}&filter=${filter}`,
{
headers: {
authorization: "Bearer " + token,
"content-type": "multipart/form-data",
},
}
);
return data;
} catch (error) {
return catchError(error);
}
};
The front end CATEGORY COMPONENT
I want the user to filter the post by category by clicking on the category
export default function AllCategory() {
const [allCategories, setAllCategories] = useState([]);
const fetchCategories = async () => {
const res = await getCategoryForUsers();
setAllCategories(res);
};
useEffect(() => {
fetchCategories();
}, []);
return (
<div className=''>
<ul className=' space-x-4 '>
{allCategories.map((c, index) => {
return <li key={index}>{c.title}</li>;
})}
</ul>
</div>
);
}
Remove the ``` as that is not how to post code here.
You'll want to use a filter first in another function
const [selectedCategories, setCurrentlySelectedCategories] = useState([]);
const handleSelectCategory = (category) => {
const currentlySelected = allCategories.filter((item) => item.category == category);
setCurrentlySelectedCategories(currentlySelected);
}
Now just call map on the selectedCategories

How to create a comment and reply section with MERN STACK

I created a comment session on my entertainment website
It’s working on backend.
It’s working on the frontend also but it’s not displaying the content the user typed on the database
This is my frontend (Comment form) logic:
export default function AddComment({ busy}) {
const [content, setContent] = useState("");
const { movieId } = useParams();
const { updateNotification } = useNotification();
const handleOnChange = ({ target }) => {
setContent(target.value);
};
const handleSubmit = async (e) => {
e.preventDefault();
const { error, message } = await addComment(movieId);
if (error) return updateNotification("error", error);
updateNotification("success", message);
const newComment = {
content,
};
setContent(newComment);
setContent("");
};
return (
<div className='p-5'>
<br />
<p className='dark:text-white text-primary'>replies</p>
<hr className='w-64' />
{/* Comment Lists */}
{/* Root Comment Form */}
{/* Form */}
<form className='flex ' onSubmit={handleSubmit} busy={busy}>
<textarea
value={content}
onChange={handleOnChange}
type='text'
autoComplete='text'
className='w-full rounded-md p-2 dark:text-white text-primary outline-none bg-transparent resize-none border-b focus:border-blue-500'
placeholder='Add New comment'
/>
<br className='dark:text-white text-primary ' />
<button
type='submit'
className=' w-5 h-14 dark:text-white text-primary bg-blue-600 hover:bg-blue-400 focus:border-blue-900 rounded-md'
>
{busy ? <ImSpinner3 className='animate-spin' /> : "Add"}
</button>
</form>
</div>
);
}
Then the addComment is coming from this API:
import { catchError, getToken } from "../utils/helper";
import client from "./client";
export const addComment = async (movieId, newComment) => {
const token = getToken();
try {
const { data } = await client.post(
`/comments/comment/${movieId}`,
newComment,
{
headers: {
authorization: "Bearer " + token,
},
}
);
return data;
} catch (error) {
return catchError(error);
}
};
The backend is working:
exports.createComment = expressAsyncHandler(async (req, res) => {
const { movieId } = req.params;
const { content } = req.body;
const userId = req.user._id;
console.log(req.body);
// verify user before comment
if (!req.user.isVerified)
return sendError(res, "Please verify your email first!");
if (!isValidObjectId(movieId)) return sendError(res, "Invalid Movie!");
// create and update new comment
const newComment = new Comment({
user: userId,
parentMovie: movieId,
content,
});
// save new comment
await newComment.save();
res.json({ message: "New comment added!!", newComment });
});
I posted with Postman on backend it gave me this on the database:
_id
:
62dcfccd93444cef55611632
user
:
62bf20d65073a7c65f549078
parentMovie
:
62c2c425465804ff32cdd06c
content
:
"hello"
createdAt
:
2022-07-24T08:03:25.666+00:00
updatedAt
:
2022-07-24T08:03:25.666+00:00
__v
:
0
on the console:
The port is listening on port 8000
connected to db
{ content: 'hello' }
POST /api/comments/comment/62c2c425465804ff32cdd06c 200 447.534 ms - 260
I posted on the frontend it gave me this on the database, no content:
_id
:
62dcfd6993444cef55611635
user
:
62bf57e8a8f3e737b2af23d9
parentMovie
:
62cc1d426785cfe42f8737a8
createdAt
:
2022-07-24T08:06:01.458+00:00
updatedAt
:
2022-07-24T08:06:01.458+00:00
__v
:
0
on the console it shows an empty object:
{}
POST /api/comments/comment/62cc1d426785cfe42f8737a8 200 364.009 ms - 242
This is how I solved the problem
Hope this solution will help many
const handleSubmit = async (e) => {
e.preventDefault();
const { error, message } = await addComment(movieId, content); // call the content and movieId from backend
if (error) return updateNotification("error", error);
updateNotification("success", message);
// push and display the content on database
const newComment = {
content,
};
setContent(newComment);
setContent("");
};
Then the API should be like this
export const addComment = async (movieId, newComment) => {
const token = getToken();
// console.log(newComment);
const body = {
content: newComment,
};
try {
const { data } = await client.post(`/comments/comment/${movieId}`, body, {
headers: {
authorization: "Bearer " + token,
},
});
return data;
} catch (error) {
return catchError(error);
}
};

how to update context state in react

I am having a problem that when user upload their profile image it did not change, user have to log out and log back in to make a change complete.
Here is my back end how to get image from client and store it on cloudinary:
profilesController.js:
exports.updateAvatar = async (req, res) => {
// Find user with matching token
// const updates = [];
const updateUserAvatar = await models.User.findOne({
where: {
id: req.id,
},
});
// Was user found?
if (updateUserAvatar === null) {
return res.status(200).json({
validationErrors: {
errors: [
{
msg: "Reset is invalid or has expired.",
},
],
},
});
}
// Update user with new info
models.User.update(
{
picture: req.imageUrl,
},
{
where: {
id: updateUserAvatar.dataValues.id,
},
}
);
console.log(updateUserAvatar);
At the console it should gave me a new image url but instead it just keep the old image url
Here is my profilesAPI where my route is:
router.post('/upload/image', function (req, res, next) {
const dUri = new Datauri();
const dataUri = (req) => dUri.format(path.extname(req.name).toString(), req.data);
if (req.files !== undefined && req.files !== null) {
const { file, id } = req.files;
const newFile = dataUri(file).content;
cloudinary.uploader.upload(newFile)
.then(result => {
const imageUrl = result.url;
const data = {id : req.body.id, imageUrl };
updateAvatar(data);
return res.status(200).json({ message: 'Success', data: { imageUrl } });
}).catch(err => res.status(400).json({message:'Error', data: { err}}));
} else {
return res.status(400).json({ message: 'Error' });
}
});
And that's all for my back end code. Here is my front end that cient send image to server:
Here is the method that help user can send image to server:
const UserCard = ({ name, userEmail, isVerified, id, updateUserAvatar, currentUser }) => {
const [selectedValue, setSelectedValue] = useState("a");
const handleChange = (event) => {
setSelectedValue(event.target.value);
};
const [imageSelected, setImageSelected] = useState("");
const uploadImage = () => {
const formData = new FormData();
formData.append("file", imageSelected);
formData.append("id", id);
axios
.post("/api/v1/profiles/upload/image", formData, {
headers: { "Content-Type": "multipart/form-data" },
})
.then((response) => {
updateUserAvatar(response.data.data.imageUrl);
});
};
useEffect(() => {
if (imageSelected !== '') {
uploadImage();
}
}, [imageSelected]);
return (
<div className="avatar--icon_profile">
<Card className="profile--card_container">
<CardContent>
{currentUser.picture ? (
<div>
<input
className="my_file"
type="file"
ref={inputFile}
onChange={(e) => setImageSelected(e.target.files[0])}
/>
<div className="profile-image">
<Avatar
src={currentUser.picture}
alt="Avatar"
className="avatar--profile_image"
onClick={onButtonClick}
/>
</div>
</div>
and here is my Global State. I tried to update nested state in my context but seems like it didn't work.
const GlobalState = (props) => {
// User State -----------------------------------------------------------------------------
const [currentUser, setUser] = useState(props.serverUserData);
console.log(currentUser)
const updateUser = (userData) => {
setUser(userData);
};
// This method is passed through context to update currentUser Avatar
const updateUserAvatar = (picture) => {
setUser({ ...currentUser, picture: picture });
};
const providerValues = {
currentUser,
updateUser,
updateUserAvatar,
};
return (
<GlobalContext.Provider value={providerValues}>
{props.children}
</GlobalContext.Provider>
);
};
export default GlobalState;
and here is my console.log(currentUser) gave me:
{id: "a19cac5c-ea25-4c9c-b1d9-5d6e464869ed", name: "Nhan Nguyen", email: "nhan13574#gmail.com", publicId: "Nh1615314435848", picture: "http://res.cloudinary.com/teammateme/image/upload/v1617229506/gnlooupiekujkrreerxn.png", …}
email: "nhan13574#gmail.com"
id: "a19cac5c-ea25-4c9c-b1d9-5d6e464869ed"
isSessionValid: true
name: "Nhan Nguyen"
picture: "http://res.cloudinary.com/teammateme/image/upload/v1617229506/gnlooupiekujkrreerxn.png"
publicId: "Nh1615314435848"
__proto__: Object
Can anyone help me solve this problem? I really appreciate it
Added GlobalContext.js:
import React from "react";
const globalStateDefaults = {
modals: {
isAuthModalOpen: false,
modalToDisplay: "signup",
toggleModal: () => {},
setModalToDisplay: () => { },
},
user: undefined,
pageName: undefined,
loading: false,
teamProfileId: "",
userProfileId: "",
};
export const GlobalContext = React.createContext(globalStateDefaults);
You need to consume the context where you are trying to update user state.
const {currentUser, updateUser, updateUserAvatar} = React.useContext(GlobalContext)
Then you can call
updateUserAvatar(response.data.data.imageUrl)

How to pass users' data from nodeJS to reactJS using Express/Mysql

I need to pass author's email in my posts. I though I can do it by joining tables in my posts route, but it doesn't really work.
Here is my route :
router.get("/posts", async (req, res) => {
const { id } = req.session.user;
//const usersPosts = await user.$relatedQuery("posts");
try {
const user = await User.query().findById(id);
if (!user) {
return res.status(401).send("User was not found");
}
const posts = await Post.query()
.select([
"users.email",
"images.name",
"posts.category",
"posts.title",
"posts.description",
"posts.created_at"
])
.join("images", { "posts.image_id": "images.id" });
.join("users", { "posts.user_email": "users.email" });
console.log("it worked");
return res.status(200).send({ posts: posts });
} catch (error) {
console.log(error);
return res.status(404).send({ response: "No posts found" });
}
});
Here is code with my axios fetching the route :
function Home(props) {
const [posts, setPosts] = useState([]);
const getPosts = async () => {
try {
let response = await axios.get("http://localhost:9090/posts", {
withCredentials: true
});
console.log(response.data.posts);
setPosts(response.data.posts);
} catch (error) {
console.log(error.data);
}
};
useEffect(() => {
getPosts();
}, []);
And this is how I tried to return it:
{posts.map((post, index) => {
return (
<>
Author:<br></br>
<small>{post.user_email}</small>
</p>
<p>
Category:<br></br>
<small>{post.category}</small>
</p>
<p>
Description:<br></br>
<small>{post.description}</small>
</p>
<p>
Created: <br></br>
<small>{post.created_at}</small>
Everything works except the fetching Author.
a typo its user_email not users_email
your sending email in the value assingned to user_email and in front end using users_email

Stripe payment method. Use card [split-form] not working. Can't get card number expiry and cvc number

I am implementing stripe with react and node.
but I am not getting these values
<CardNumberElement/>
<CardExpiryElement/>
<CardCvcElement/>
Without its value can't get token and can't charge money.
if I only use cardElement that's work but cardElement is a one-line input field but I want to split it. so that's why I used CardNumberElement , CardExpiryElement, and CardCvcElement for splitting.
backend code is perfect but the error is inside frontend because we can't pass values perfectly for creating the stripe token for the payment.
Frontend Code:
import React from "react";
import { loadStripe } from "#stripe/stripe-js";
import {
Elements,
CardElement,
useStripe,
useElements,
CardNumberElement,
CardExpiryElement,
CardCVCElement,
injectStripe,
StripeProvider,
CardCvcElement
} from "#stripe/react-stripe-js";
import axios from "axios";
import { ServiceBooking } from "../../services/service-booking"
const CheckoutForm = ({ success }) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async event => {
event.preventDefault();
let number = elements.getElement(CardNumberElement);
let cvc = elements.getElement(CardCvcElement);
console.log("farrukh",number)
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: "card",
card: {
number: number,
exp_month: 4,
exp_year: 2021,
cvc: cvc,
}
});
if (!error) {
const { id } = paymentMethod;
try {
const data = await ServiceBooking.charge(id, 1099);
console.log(data);
success();
} catch (error) {
console.log(error);
}
}
};
return (
<form
onSubmit={handleSubmit}
style={{ maxWidth: "400px", margin: "0 auto" }}
>
<h2>Price: $10.99 USD</h2>
<CardNumberElement/>
<CardExpiryElement/>
<CardCvcElement/>
<button type="submit" disabled={!stripe}>
Pay
</button>
</form>
);
};
// you should use env variables here to not commit this
// but it is a public key anyway, so not as sensitive
const stripePromise = loadStripe("pk_test_wSvr6guTJvkKmv21jVqVd2D20049BVPKHP");
const Checkout = () => {
const [status, setStatus] = React.useState("ready");
if (status === "success") {
return <div>Congrats on your empanadas!</div>;
}
return (
<Elements stripe={stripePromise}>
<CheckoutForm
success={() => {
setStatus("success");
}}
/>
</Elements>
);
};
export default Checkout;
Backend Code:
router.post(
"/charge",
asyncHandler(async function (req, res) {
const { id, amount } = req.body;
try {
const payment = await stripe.paymentIntents.create({
amount,
currency: "USD",
payment_method: id,
confirm: true
});
console.log(payment);
return res.status(200).json({
confirm: "abc123"
});
} catch (error) {
console.log(error);
return res.status(400).json({
message: error.message
});
}
})
);
I have tried but could not charge (payment) successfully
in Stripe.js, you would just need to pass the CardElement to the stripe.createPaymentMethod call. You do not need the card number nor you can get the card number due to security reason.
let number = elements.getElement(CardNumberElement);
...
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: "card",
card: number
});
You may ask how do I pass in the cvc and expiry date? The answer is that you don't have to, Stripe.js will automatically locate the CVC and expiry input automatically in the same page.
See reference at https://stripe.com/docs/stripe-js/react
You need to change your front-end code. For a split form to work, you should use three different (card) keys under .createPaymentMethod to catch the CardNumberElement, CardExpiryElement, and CardCvcElement respectively
.createPaymentMethod(
{
type: 'card',
card: elements.getElement(CardNumberElement),
card: elements.getElement(CardExpiryElement),
card: elements.getElement(CardCvcElement),
}
Your Frontend Code should look like this:
import React from "react";
import { loadStripe } from "#stripe/stripe-js";
import {
Elements,
CardElement,
useStripe,
useElements,
CardNumberElement,
CardExpiryElement,
CardCVCElement,
injectStripe,
StripeProvider,
CardCvcElement
} from "#stripe/react-stripe-js";
import axios from "axios";
import { ServiceBooking } from "../../services/service-booking"
const CheckoutForm = ({ success }) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async event => {
event.preventDefault();
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardNumberElement),
card: elements.getElement(CardExpiryElement),
card: elements.getElement(CardCvcElement),
});
if (!error) {
const { id } = paymentMethod;
try {
const data = await ServiceBooking.charge(id, 1099);
console.log(data);
success();
} catch (error) {
console.log(error);
}
}
};
return (
<form
onSubmit={handleSubmit}
style={{ maxWidth: "400px", margin: "0 auto" }}
>
<h2>Price: $10.99 USD</h2>
<CardNumberElement/>
<CardExpiryElement/>
<CardCvcElement/>
<button type="submit" disabled={!stripe}>
Pay
</button>
</form>
);
};
// you should use env variables here to not commit this
// but it is a public key anyway, so not as sensitive
const stripePromise = loadStripe("pk_test_wSvr6guTJvkKmv21jVqVd2D20049BVPKHP");
const Checkout = () => {
const [status, setStatus] = React.useState("ready");
if (status === "success") {
return <div>Congrats on your empanadas!</div>;
}
return (
<Elements stripe={stripePromise}>
<CheckoutForm
success={() => {
setStatus("success");
}}
/>
</Elements>
);
};
export default Checkout;
Just for anyone coming up with this issue in 2022. I was having a similar set up as OP and the answer is here on YouTube. You can use the method .confirmCardPayment taking any of the elements. Something like:
(token) => {
stripe?.confirmCardPayment(token, {
payment_method: {
card: elements?.getElement(CardNumberElement),
billing_details: {
name: "Leandro",
},
},
});
}
it will still validate all fields. Testing in the sandbox, I was passing like so but testing with leaving CVC empty, and stripe API was rejecting.

Resources