I'm trying to use AJAX for my contact form which sends the form data to the server. Which should then email me users information through the input fields.
The issue I'm having is, the formData seems to doing as normal (appears in the network on the browser) But when my email comes through i'm getting undefined values?
const submit = document.querySelector('.contact-btn');
submit.addEventListener('click', send);
function send(event){
event.preventDefault();
const url = "https://us-central1-selexin-website.cloudfunctions.net/app/sendemail";
let form = document.querySelector('form');
let formData = new FormData(form);
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState === XMLHttpRequest.DONE){
console.log(formData)
// Make a pop up message in green below the textarea box to notify user email was sent.
}
}
xhr.open('POST', url, true);
xhr.send(formData);
};
Below is the field being emailed to me. As you can see, in the email body I've added "run test"as a string and that returns perfect in the email. Why is req.body giving me undefined values?
const transport = nodemailer.createTransport(sendgridTransport({
auth: {
api_key: apiKey
},
}));
app.use(express.urlencoded({extended: false}));
app.use(cors({ origin: true }));
app.post('/sendemail', (req, res) => {
const {name, email, number, message} = req.body;
return transport.sendMail({
to: 'email receiving',
from: 'from this email',
subject: 'New Contact Request',
html: `
<p>You have a new Contact Request</p>
<h3>Contact Details</h3>
<ul>
<li>Name: 'Run test'</li>
<li>Email: ${email}</li>
<li>Number: ${number}</li>
<li>Message: ${message}</li>
</ul>
`
}).then(() => {
if(res.sendStatus(200)){
console.log('it logs');
};
})
});
exports.app=functions.https.onRequest(app);
You're sending your request body as multipart/form-data, not application/x-www-form-urlencoded.
If you wanted to handle the former, you'd need something like the Multer middleware in your express app.
The quick and easy solution is to wrap your FormData in URLSearchParams
xhr.send(new URLSearchParams(formData))
This will post your data as application/x-www-form-urlencoded which is handled by the express.urlencoded() middleware you're already using.
I also highly recommend adding the event listener to your form's submit event instead of a button click. That way, you can catch things like "type Enter to submit"
document.querySelector("form").addEventListener("submit", e => {
e.preventDefault()
const formData = new FormData(e.target)
// ...and the rest of your "send" logic
})
Related
So I am creating a quote request contact form on a website and I need to make a confirmation or error message pop up under the form after it has been submitted. The issue I am faced with is how can I set a variable on the express side based on whether there was an error or not with the email sending and then use that variable within my handlebars template in order to display the proper message. I'm thinking I would use a helper to achieve this but I keep hitting a wall on attempting it. The logic should begin withing the transporter.sendMail as that is where the error will be determined. I put comments in to help identify.
Here is the backend of my contact form:
// ==== Contact Form ====
//Create Mailer options
const options = {
viewEngine: {
extname: '.hbs',
layoutsDir: __dirname + '/views/email/',
defaultLayout: 'template',
partialsDir: 'views/partials/'
},
viewPath: 'views/email/',
extName: '.hbs'
};
// Create Transporter
const transporter = nodemailer.createTransport({
host: 'smtp-mail.outlook.com',
port: 587,
auth: {
user: process.env.USER,
pass: process.env.PASS
}
});
// verify connection configuration
transporter.verify(function(error, success) {
if (error) {
console.log('Error with transporter verification:' + `\n${error}`);
}
});
//attach the plugin to the nodemailer transporter
transporter.use('compile', hbs(options));
app.post('/send', (req, res) => {
// Accepts the form data submitted and parse it
let form = new multiparty.Form();
let data = {};
form.parse(req, function(err, fields) {
Object.keys(fields).forEach(function(property) {
data[property] = fields[property].toString();
});
// Create Mail object with options
const mail = {
from: `"********" <${process.env.USER}>`,
to: '************', // receiver email,
subject: 'Quote Request',
template: 'email.body',
// Import variables into email for use with handlebars
context: {
name: data.name,
email: data.email,
number: data.number,
message: data.message
}
};
// Send email
transporter.sendMail(mail, (err, data) => {
if (err) {
console.log(err);
// if error return mailError = true;
}
else {
console.log('Email successfully sent to recipient!');
// if sent return mailSent = true;
}
});
});
});
Here is my script.js:
// Contact Form Client Functions
//get the form by its id
const form = document.getElementById('contact-form');
//add event listener (when clicking the submit button, do the following)
const formEvent = form.addEventListener('submit', (event) => {
// Prevent page from refreshing when submit button clicked
event.preventDefault();
//parse data to formData variable
let mail = new FormData(form);
//send mail
sendMail(mail);
// Determine if sendMail returned an error or not
console.log(typeof mailError);
// reset form feilds to empty
form.reset();
});
const sendMail = (mail) => {
console.log('step 1');
fetch('/send', {
method: 'post',
body: mail
}).then((response) => {
return response.json();
});
};
and here is the section within my template.hbs file that I need dynamically updated:
<div>
{{#if mailSent}}
<h4 style="color: lightgreen">Your message has been sent successfully!</h4>
{{else if mailError}}
<h4 style="color: red">ERROR: There was an issue sending your message, please
try again.</h4>
{{/if}}
</div>
I think you are mixing Server Side Rendering vs Client Side Rendering strategies (I suggest you to read this to understand the difference). Typically you'd want to use one or the other.
Server Side Rendering Approach: Here is a quick StackBlitz example I did based on your code using server side rendering that you can play with. The basic idea with this strategy is to let your express route render the response (using Handlebars):
app.post('/send-email', (req, res) => {
// proceed to send email
sendEmail((err, data) => {
// render view based on response:
res.render('form', {
sent: !err,
message: err?.message,
});
});
});
Notice how res.render is used in this case, we are not sending a JSON response but the direct view result instead, which would look something like this:
<form action="/send-email" method="POST">
<h1>Send Email</h1>
<p>Click send to get a random response!</p>
<input type="email" placeholder="Enter your email" value="test#mail.com" required />
<input type="submit" value="Send" />
</form>
<div class="msg">
{{#if sent}}
<h4 style="color: lightgreen">Your message has been sent successfully!</h4>
{{else if message}}
<h4 style="color: red">
ERROR: There was an issue sending your message, please try again.
<br />
Original server error: {{message}}
</h4>
{{/if}}
</div>
<script>
document.querySelector('form').addEventListener('submit', () => {
document.querySelector('.msg').style.display = 'none';
});
</script>
Notice also how we don't use Javascript here to send the request, just the default behavior of <form> to make the request. This will cause the page to reload.
Client Side Rendering Approach: Here is the same example slightly modified to use AJAX and fetch API.
Now our endpoint must return a JSON response that the client can use to react accordingly:
app.post('/send-email', (req, res) => {
sendEmail((err, data) => {
res.status(!err ? 200 : 500).json({
sent: !err,
message: err?.message,
});
});
});
Then we let the client side Javascript handle the request and subsequent update of the DOM:
<form>
<h1>Send Email</h1>
<p>Click send to get a random response!</p>
<input type="email" name="email" placeholder="Enter your email" value="test#mail.com" required />
<input type="submit" value="Send" />
</form>
<div class="msg">
<h4 class="feedback"></h4>
</div>
<script>
function sendMail(mail) {
return fetch('/send-email', {
method: 'post',
body: mail,
}).then(function (response) {
return response.json();
});
}
var msgContainer = document.querySelector('div.msg');
msgContainer.style.display = 'none';
document.querySelector('form').addEventListener('submit', function (e) {
e.preventDefault();
msgContainer.style.display = 'none';
var mail = new FormData(e.target);
sendMail(mail).then(function (res) {
var message = res.sent
? 'Your message has been sent successfully!'
: 'ERROR: There was an issue sending your message, please try again.';
var feedback = document.querySelector('h4.feedback');
feedback.textContent = message;
feedback.style.color = res.sent ? 'lightgreen' : 'red';
msgContainer.style.display = 'block';
});
});
</script>
This will NOT cause the page to reload.
When the form is empty, pressing the Login button opens the page. I want him to make a mistake. I'm already getting an error if it's not compatible with the server. How can I solve this problem ?
const login = (e) => {
e.preventDefault();
axiox.post("http://localhost:2000/api/auth/login", {
email,
password
}).then((response) => {
console.log("response", response)
localStorage.setItem("login", JSON.stringify({
userLogin: true,
token: response.data.access_token,
}));
setError("");
setEmail("");
setPassword("");
history.push("schools");
})
.catch((error) => setError(error.response.data.message));
};
use this to validate the form
const login = (e) => {
e.preventDefault();
if(!email || !password){
//send error message to user
}else{
// make request to api
}
};
A simple and efficient way is to check any errors at the client-side i.e if the form is empty you should not make an API call. The validation on the form or in your function to check if the values are null is enough.
You can add more validations on the form, however, certain validations which are business mandate could be done at the server-side.
const login = (e) => {
e.preventDefault();
// make request to api
if(!email && !password) {
//here make the API call to authenticate a user
}
};
Check this link to get more details, how to add the validation in React Forms.
I think you want the form to respond with an error message when they haven't filled up the form. Here:
const login = (e) => {
e.preventDefault();
if(!email || !password){
//Send the error message, like alert('Either the Email or Password is empty!')
}else{
//The code to execute when both Email and Password are filled.
}
};
Cannot understand even if i delete all inside function and just print something still got this error, but when i use fastapi docs, and try signing with that, it work.
#auth_router.post('/signin')
async def sign_in(username: str = Form(...), password: str = Form(...)) -> dict:
user = await authenticate_user(username, password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid username or password',
)
user_obj = await User_Pydantic.from_tortoise_orm(user)
user_token = await generate_token(user_obj)
return {
'access_token': user_token,
'token_type': 'bearer',
}
Before i use OAuth2PasswordRequestForm, when got 422 error, try another way.
my model is tortoise orm, and when need i convert it to pydantic model,
in docs all is work.
JS
handleEvent(signinform, 'submit', e => {
e.preventDefault();
if(!isEmpty(signinform)){
signInUsername = getElement('input[name="username"]', signinform).value;
signInPassword = getElement('input[name="password"]', signinform).value;
recaptchaV3 = getElement('[name="g-recaptcha-response"]').value;
if(recaptchaV3){
signInData = new FormData();
signInData.append('username', signInUsername);
signInData.append('password', signInPassword);
isLogened = request('POST', '/signin', signInData);
if(isLogened){
log(isLogened);
}
} else{
alert('Reload Page');
}
}
})
authenticate_user func
async def authenticate_user(username: str, password: str):
user = await User.get(username=username)
if not user or not user.verify_password(password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid username or password',
)
return user
My request function
const request = (method, url, data = null) => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onerror = function () {
console.log(xhr.response);
};
xhr.onload = () => {
if (xhr.status === 200) {
return resolve(JSON.parse(xhr.responseText || '{}'))
} else {
return reject(new Error(`Request failed with status ${xhr.status}`))
}
}
if (data) {
xhr.send(JSON.stringify(data))
} else {
xhr.send()
}
})
}
Although you did not publish the error, who's purpose is to tell you the problem, I'm fairly sure the problem lies in the way you perform the request.
The line
xhr.setRequestHeader('Content-Type', 'application/json')
means that you are sending json data, which is not accepted by the authentication form of openapi. Also, you are stringifying the data into json which, again, is not an accepted format.
Thus, changing the content type to www-form-urlencoded and adding a FormData object to your request's body, will make it work.
You can see it in the github discussions below
https://github.com/tiangolo/fastapi/issues/2740
https://github.com/tiangolo/fastapi/issues/1431
Ensure that you have provided content type in your request,
xhr.setRequestHeader('Content-Type', 'application/json')
If it's there then verify the format of your input. Here, you have declared two varibales - signInUsername and signInPassword.
Important: You might provided a default value for these files in FastAPI. In that case, if you provided an empty content or null as the attribute values then the fastapi will throw the above error.
Ensure that the data that you are sending to the server is correct.
so I'm trying to make a web application that stores the user. I want to use this user in the back-end to retrieve information. So right now a button is clicked and that calls the back-end, the onClick function currently looks like this.
const getBackend = () => {
let url = new URL('https://http://localhost:4000/backend');
url.search = new URLSearchParams({
username: user,
});
fetch(`http://localhost:4000/prize`, {
method: "GET",
url:url,
});
}
and my express.js server looks like this:
app.get("/backend", async (req, res) => {
let user = req.query.username;
console.log("user");
res.send(user);
}
What happens is that when I click the button from the front end with the getBackend function the console and browser will log the user as "undefined". However, when I put the link on the browser directly as: http://localhost:4000/prize?username=test3%40something.edu I successfully get "test3#something.edu" working. How can I make the button give me the same as putting it on the browser?
Thank you.
I think you accidently added the https in the url. You should leave off the https and just keep the http
const getBackend = () => {
let url = new URL('http://localhost:4000/backend');
url.search = new URLSearchParams({
username: user,
});
fetch(`http://localhost:4000/prize`, {
method: "GET",
url:url,
});
}
This is the code and i want twilio to read from a route which is a post route but it doesnt read xlm from there and there i want to get digit from a user and according to that input during call then i want the flow to work based on that input.
.create({
method: 'POST',
url: 'https://stormy-everglades-64562.herokuapp.com/voice',
to: '+923047931504',
from: '+17207344485'
})
.then(call => console.log(call.sid));
app.post('/voice', (request, response) => {
// Create TwiML response
const twiml = new VoiceResponse();
twiml.gather(
{
numDigits: 1,
action: '/gather',
},
gatherNode => {
gatherNode.say('For sales, press 1. For support, press 2.');
}
);
// If the user doesn't enter input, loop
twiml.redirect('/voice');
// Render the response as XML in reply to the webhook request
response.type('text/xml');
response.send(twiml.toString());
});```
Try the below:
app.post('/voice', (request, response) => {
// Create TwiML response
const twiml = new twilio.twiml.VoiceResponse();
const gather = twiml.gather(
{
numDigits: 1,
action: '/gather',
})
gather.say('For sales, press 1. For support, press 2.');
// If the user doesn't enter input, loop
twiml.redirect('/voice');
// res.set('Content-Type','text/xml');
response.type('text/xml');
response.send(twiml.toString());
});